BE 설정 과정 및 예시 프로젝트

김민지·2024년 9월 25일

Node.js Step By Step Guide

1. BE 서버 초기 환경 설정

  1. node.js 다운로드 확인
node -v
npm -v
  1. Express 설치 → node.js 프로젝트 생성
npm install express
  1. nodemon 패키지 설치
  • nodemon : 코드를 수정했을 때 서버를 즉시 재시작해 주는 툴
  • 해당 패키지는 서버를 개발할 때만 필요하고 서버를 배포해서 운영할 때는 필요하지 않기 때문에 —-save-dev 옵션을 사용한 겁니다.
npm install --save-dev nodemon
  1. VSCode extention REST Client 설치

  2. package.json"type": "module" 추가
    ▷ type을 module로 설정해야 import, export 같은 ES 모듈 문법을 사용

{
  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
  },
  "type": "module" // 추가!
}

2. 이후 request 핸들링 및 코드 작성

(1) 로컬 웹브라우저 사용 예시

  • 예시 코드로 아래의 기본 코드를 작성한 후, VSCode의 콘솔창에 node app.js를 입력하고 로컬 웹브라우저의 주소창에 localhost:3000/hello 등으로 요청을 보내면 자동으로 GET request가 되어서 화면에 Hello Express!가 출력되고, VSCode의 콘솔창에는 Server Started 메시지가 출력됨
// app.js
import express from 'express';
const app = express();

app.get('/hello', (req, res) => {
  res.send('Hello Express!');
}) 

app.listen(3000, () => console.log('Server Started'));
  • res.send : js 객체를 받아서 json으로 변환해서 반환
    덕분에 DB 연결 전에도 js 파일로 아래의 예시와 같은 mock data를 생성해서 app.js 파일에서 import tasks from './data/mock.js 를 부르고, app.get('/tasks', 로 변경하고 send(tasks) 로 수정해서 테스트해볼 수 있음
// data/mock.js
const data = [
    {
        id: 1,
        title: "Learn JavaScript",
        isComplete: false,
        createdAt: "2024-09-29T10:00:00Z",
        updatedAt: "2024-09-29T12:00:00Z"
    },
    {
        id: 2,
        title: "Write a blog post",
        isComplete: true,
        createdAt: "2024-09-28T09:30:00Z",
        updatedAt: "2024-09-29T08:00:00Z"
    },
    {
        id: 3,
        title: "Go for a walk",
        isComplete: false,
        createdAt: "2024-09-27T07:15:00Z",
        updatedAt: "2024-09-27T09:45:00Z"
    }
];
export default data;
  • 그러나 주소창에서는 GET request만 보낼 수 있어서 request를 다양하게 테스트 하는것에 제한이 있음
    ▷ 범용성을 위해 Rest Client 사용!

(2) Rest Client 사용 예시

  • requests.http 파일 생성
  • 위와 같은 request를 보내보기 위해서는, GET localhost:3000/hello 라고 작성하면 됨
  • 만약 여러개의 request를 한 파일에서 작성해두고 하나씩 보내보고자 하는 경우, ###로 구분해야 함
GET http://localhost:3000/hello

###

GET http://localhost:3000/tasks?sort=oldest&count=3

(3) Nodemon 사용 예시

  • 문제점
    만약 우리가 위의 js 코드에서 res.send 부분을 res.send('Bye Express!'); 로 수정하고 다시 request를 보낸다고 해도 바로 반영되지 않고, Ctrl+C로 서버를 끈 후 다시 시작해야 변경사항을 반영한 request가 나타남
    → 개발 상황에서 번거로움 ..

  • 해결
    이를 해결하기 위해 nodemon으로 서버를 시작해주면 js 코드 수정 → 저장 → request 다시 보내기의 과정만 거치면 바로 수정된 버전으로 request가 전송됨

  • nodemon으로 서버를 시작하는 방법? nodemon app.js

  • "scripts" 필드 수정
    시작하는 명령어가 여러개가 되었으므로 이를 간단하게 하기 위해서 package.json에 단축어("scripts" 필드) 추가

  • 서버 시작
    "scripts" 필드에 저장된 명령어를 실행하려면 npm run 명령어 를 입력해야 하므로,
    ▷ 개발 환경에서 서버시작(nodemon app.js): npm run dev
    ▷ 실제 환경에서 서버시작(node app.js): npm run start

{
  "dependencies": {
    "express": "^4.21.0"
  },
  "devDependencies": {
    "nodemon": "^3.1.7"
  },
  "type": "module",
  "scripts": {
    "dev": "nodemon app.js",
    "start": "node app.js"
  }
}

(4) Query String

  • 정의: http에서 ? 뒤에 나오는 일종의 조건문
    예: sort=oldest&count=3 에서는 오래된 순서대로 정렬하고, 3개만 출력하라는 조건을 걸어주는 쿼리스트링

  • 쿼리스트링 처리를 위해서는 app.js에서 미리 처리를 해줘야 함

// app.js
import express from 'express';
const app = express();

app.get('/hello', (req, res) => {
  // 쿼리 파라미터 설명: 
  // sort: 'oldest'인 경우 오래된 테스크 기준, 나머지 경우 새로운 테스크 기준!
  // count: 테스크 개수
  const sort = req.query.sort;
  const count = Number(req.query.count);  
  const compareFn = 
        sort === 'oldest'
  			? (a, b) => a.createdAt - b.createdAt
  			: (a, b) => b.createdAt - a.createdAt;
  // 여기까지 쿼리파라미터 처리를 위한 조건문
  res.send(tasks);
}) 

app.listen(3000, () => console.log('Server Started'));

(5) Dynamic URL 처리

  • url에 변수가 들어간다고 생각하면 됨
    예: /tasks/1, /tasks/2, ... 등 task의 id별로 url 리퀘스트를 각각 생성할 수는 없으므로 /tasks:id 처럼 다이나믹하게 변할 수 있는 url을 생성해서 사용함
  • 이 때, url 중 값이 바뀌는 부분은 :으로 연결하며 url parameter라고 부르며, 기본적으로 req.params.id 등으로 불러오며 문자열로 정의되므로 만약 숫자로 사용하고자 하면 Number()로 바꿔줘야 함
// app.js
import express from 'express';
const app = express();

app.get('/tasks:id', (req, res) => {
  // 다이나믹 URL
  const id = Number(req.params.id);
  const task = tasks.find((task) => task.id === id);
  if (task) {
    res.send(task);
  } else {
    res.status(404).send({ message: "Cannot find given id" });
  }
}) 

app.listen(3000, () => console.log('Server Started'));
  • 이 때, 리퀘스트를 보내는 URL은 GET http://localhost:3000/tasks/1 등으로 작성할 수 있다.

(6) POST request 생성

  • 아래의 코드와 같이 request를 보낼 URL을 작성하고 바로 아랫줄에 Content-Type를, 그리고 한 줄을 띄우고 json body 작성
POST http://localhost:3000/tasks
Content-Type: application/json 

{
	"title": "산책",
    "description": "아파트단지 걷기"
}
  • 단, Express는 request body로 전달되는 json 객체를 js 객체로 자동으로 전환해주지 않음
    parsing: jsonjs 객체로 변환하는 과정

  • parsing을 하려면 app.use(express.json()) 로 app 전체에서 express.json 객체를 사용하도록 설정해야 함
    request의 content-type이 application json이면 body를 parsing해서 js 객체로 만들고, request의 body property에 담아줌

// app.js
import express from 'express';
const app = express();
app.use(express.json()) // parsing 설정

app.post('/tasks', (req, res) => {
  // 추후 DB로 대체될 예정
  const newTasks = req.body;
  const ids = tasks.map((task) => task.id);
  newTask.id = Math.max(...ids) + 1;
  newTask.isComplete = false;
  newTask.ereatedAt = new Date();
  newTask.updatedAt = new Date();
  
  // post하기
  tasks.push(newTask);
  res.status(201).send(newTask);
}) 

app.listen(3000, () => console.log('Server Started'));
  • 잘 작성되었는지 테스트해보려면, POST http://localhost:3000/tasks를 실행하고 나서 GET으로 바꿔서 tasks를 다시 한번 받아오며 tasks가 추가되었는지를 확인

(7) PATCH request 생성

  • 기존의 POST request와 유사한 형태의 request 작성
PATCH http://localhost:3000/tasks/1 // 수정하고자 하는 task의 id(1)로 변경
Content-Type: application/json 

{
	"isComplete": true // 수정하고자 하는 필드들 + 수정값들 설정
}
  • id에 해당하는 코드 가져오기
    만약 task가 있다면 request body에 전송되는 내용을 task 객체에 덮어써야 함
// app.js
import express from 'express';
const app = express();

app.patch('/tasks:id', (req, res) => {
  // id에 해당하는 코드 가져오기
  const id = Number(req.params.id);
  const task = tasks.find((task) => task.id === id);
  if (task) { // 만약 task가 있다면,
    // request body에 전송되는 내용을 task 객체에 덮어쓰기
    Object.keys(req.body).forEach((key)=>{
      task[key] = req.body[key];
    });
    // task의 updatedAt field를 최신 시간으로 설정
    task.updatedAt = new Date();
    res.send(task);
  } else {
    res.status(404).send({ message: "Cannot find given id" });
  }
}) 

app.listen(3000, () => console.log('Server Started'));
  • 지금은 DB를 사용하고 있지 않은 상태이므로 POST, PATCH로 수정한 데이터는 서버를 껐다가 켜면 삭제됨. DB 연결로 해결 가능.

(8) DELETE request 처리

  • delete request는 삭제할 request만 특정해주면 되므로 body가 필요하지 않음
DELETE http://localhost:3000/tasks/1 // 삭제하고자 하는 task의 id(1)로 변경
// app.js
import express from 'express';
const app = express();

app.delete('/tasks:id', (req, res) => {
  // 다이나믹 URL
  const id = Number(req.params.id);
  const task = tasks.findIndex((task) => task.id === id);
  if (idx >= 0) { // id를 가진 task가 없다면 idx === -1이 되므로, 
    tasks.splice(idx, 1); // 인덱스 idx에서 시작해서 요소 1개를 지워라
    res.sendStatus(204)// sendStatus: body 없이 상태코드만 보내고자 할 때
  } else {
    res.status(404).send({ message: "Cannot find given id" });
  }
}) 

app.listen(3000, () => console.log('Server Started'));

3. DB 생성 및 서버와 연결: mongoDB 사용

(1) 초기 환경 설정

  1. MongoDB Atlas 에서 DB 생성, 연결 URL 준비하기

  2. 프로젝트 최상위 디렉토리에 env.js 파일 생성 후 준비된 연결 URL 붙여넣기

export const DATABASE_URL =
  'mongodb+srv://codeit:<password>@mongodb-cluster.t5eg2vz.mongodb.net/todo-api?retryWrites=true&w=majority';
  1. Mongoose 라이브러리 설치: Mongoose는 자바스크립트를 사용해서 MongoDB 데이터베이스와 소통할 수 있게 해 주는 라이브러리로, Mongoose가 제공하는 API를 이용해서 데이터베이스에 접속하고 CRUD(Create, Read, Update, Delete) 연산을 할 수 있는 게 됨
npm i mongoose
// app.js (api 작성된 파일)에서 다음과 같이 작성
import mongoose from 'mongoose';
import { DATABASE_URL } from './env.js';

// ...

mongoose.connect(DATABASE_URL).then(() => console.log('Connected to DB'));
  • mongoose.connect()메소드에 DATABASE_URL을 전달해서 데이터베이스에 접속하는 작업은 비동기로 이루어지기 때문에 .then() 메소드를 활용해서 접속되면 Connected to DB라는 메시지를 출력하도록 함
  1. DB에 접속
    npm run dev 를 실행하면 이제는 Server Started와 Connected to DB가 함께 출력됨

(2) MongoDB 설정 정의

  1. 스키마 정의하기: models 폴더 생성 → 폴더 안에 Task.js 파일 생성
// 예제: Task.js
import mongoose from 'mongoose';

const TaskSchema = new mongoose.Schema(
  {
    // id는 항상 unique하게 자동으로 생성되므로 작성할 필요 없음
    title: {
      type: String,
    },
    description: {
      type: String,
    },
    isComplete: {
      type: Boolean, 
      default: false,
    },
  }
  {
    // schema에 대한 옵션은 두번째 파라미터로 전달
    timestamps: true, 
  }
);

const Task = mongoose.model('Task', TaskSchema); // 'Task'는 mongoDB의 collection 이름

export default Task;
  1. 시드데이터 넣기
    (1) data 폴더 아래에 mock.js, seed.js 파일 생성
    mock.js 파일에는 데이터 객체를 담은 배열 형태로 데이터를 담아두고, seed.js에서 불러와서 사용
// seed.js
import mongoose from 'mongoose';
import { Groupdata, Commentdata, Postdata } from './mock.js';

import Group from '../models/Group.js';
import Comment from '../models/Comment.js';
import Post from '../models/Post.js';

import { DATABASE_URL } from '../env.js';

mongoose.connect(DATABASE_URL);

// Group
await Group.deleteMany({});
await Group.insertMany(Groupdata);

// Comment
await Comment.deleteMany({});
await Comment.insertMany(Commentdata);

// Post
await Post.deleteMany({});
await Post.insertMany(Postdata);

mongoose.connection.close(); // DB 연결 종료
// mock.js
const Groupdata = [
  {
    // id 필드 필요없음
    title: 'rweoicm', 
  },
];

const Commentdata = [
  {
    // id 필드 필요없음
    title: 'rweoicm', 
  },
];

const Postdata = [
  {
    // id 필드 필요없음
    title: 'rweoicm', 
  },
];

// export default data;
export { Groupdata, Commentdata, Postdata };

(2) 작성한 seed.js 파일을 실행하기 위해 package.jsonscripts 에 아래와 같이 단축어 추가 후 npm run seed 로 실행해서 mongoDB에 데이터 추가

"scripts": {
  "dev": "nodemon app.js",
  "start": "node app.js",
  "seed": "node data/seed.js",
}

✨ 초기에는 mock.js만 사용해서 mongoDB 연결 없이 app.js를 작성해야 rest client를 활용해서 로직이 잘 동작하는지 확인할 수 있음. 그 이후 데이터베이스를 연결한 후 조금의 코드만 수정해주면 됨.

4. Render 이용해서 배포

1. 환경변수 설정: dotenv 라이브러리 사용

(1) dotenv 라이브러리 설치

npm install dotenv

(2) .env 파일 생성 (여기에서는 큰따옴표를 사용해야 함)

DATABASE_URL="mongodb+srv://<username>:<password>@todo-api.l0aepsl.mongodb.net/todo-api?retryWrites=true&w=majority"
PORT=3000

(3) app.js, seed.jsDATABASE_URL 가져오는 부분을 변경사항에 맞춰 수정하기

// 수정 전
import { DATABASE_URL } from './env.js';

// ...

mongoose.connect(DATABASE_URL).then(() => console.log('Connected to DB'));

// ...

app.listen(3000, () => console.log('Server Started'));
// 수정 후
// ...

import * as dotenv from 'dotenv';
dotenv.config();

// ...

mongoose.connect(process.env.DATABASE_URL).then(() => console.log('Connected to DB'));

// ...

app.listen(process.env.PORT || 3000, () => console.log('Server Started'));

2. CORS 설정하기

  1. cors 패키지 설치 후 app.js 파일 업데이트
npm install cors
// app.js

import cors from 'cors';

// ...

const app = express();

app.use(cors());
app.use(express.json());
  • 필요 시 특정 주소에 대해서만 CORS를 허용할 수도 있음
const app = express();

const corsOptions = {
  origin: ['http://127.0.0.1:5500', 'https://my-todo.com'],
};

app.use(cors(corsOptions));
app.use(express.json());

3. Github에 업로드

(1) VSCode 터미널에서 순서대로 아래 커맨드 실행

git init
echo 'node_modules/\n.env\n*.http\n*.log\n*.gz\n.DS_Store' > .gitignore
  • 두 번째 줄은 Git 파일 관리에서 제외할 것들을 명시하는 .gitignore 파일을 생성: node_modules 디렉토리, .env 파일, .http 파일, 그리고 다른 것들 몇 가지는 GitHub에 올릴 필요가 없기 때문에 제외

(2) 좌측 메뉴에서 아래의 사진과 같이 Source Control을 선택하고 Changes 옆의 + 버튼을 클릭한 후 밑줄 친 부분에서 commit message를 작성하고 commit 버튼을 누르기
(3) push하고자 하는 github repository로 들어가면 초기세팅 페이지가 나올 때, 아래에 붉은 색으로 표시된 코드를 복사해서 VSCode 터미널에 붙여넣고 실행하기
→ github에서 새로고침해서 commit이 반영되었는지 확인하기

4. Render에서 새로운 웹서비스를 생성하고 나의 Github와 연결

(1) render 회원가입 후 아래의 사진과 같이 webservice 클릭

(2) github 선택

5. Render의 dashboard에서 시작 명령어, 환경변수 설정

  • DB_URI, PORT, ... etc

예시를 참고해서 진행한 토이프로젝트 설명

🌌 조각집

조각집이란? 기억 저장 및 공유 서비스입니다.

개발 시 유의할 점

  • API 무한 루프 주의
  • 너무 많은 양(약 1000개 이상)의 더미 데이터를 넣지 말 것

FE와의 연결 !


본 페이지에 기록한 코드를 제외한 예시 프로젝트의 아이디어, api 명세서, 기능 아이디어 등을 포함하는 토이프로젝트의 모든 자료는 저작권법에 의해 보호받는 ㈜코드잇의 자산을 학습 목적으로 제공받아 사용했으며 무단 사용 및 도용, 복제 및 배포를 금합니다.

Copyright 2024 코드잇 Inc. All rights reserved.

초기세팅 출처: https://www.codeit.kr/topics/intro-to-javascript-backend/lessons/6302
Express로 API 만들기 출처: https://www.codeit.kr/topics/intro-to-javascript-backend/lessons/6317

0개의 댓글