Express를 통해 MongoDB를 사용하고, REST API를 설계하고 구현하여 할일 메모를 만들어보자!
- 할 일 추가하기
- 할 일 목록 보기
- 할 일 내용 변경하기
- 할 일 순서 변경하기
- 할 일 완료하기
- 할 일 완료 해제하기
먼저 vscode를 열고 프로젝트 폴더를 만든뒤, 터미널을 열어 express, mongoose 라이브러리를 설치한다.
# 프로젝트를 초기화
yarn init -y
# express와 mongoose를 yarn을 이용해 설치
yarn add express mongoose
// package.json
{
"name": "todo-list",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
🤡 "type": "module",🤡
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.4.0"
}
}
import express from 'express';
const app = express();
const PORT = 3000;
// Express에서 req.body에 접근하여 body 데이터를 사용할 수 있도록 설정합니다.
// app.use 는 미들웨어를 사용하게 해주는 코드
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const router = express.Router();
router.get('/', (req, res) => {
return res.json({ message: 'Hi!' });
});
app.use('/api', router);
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
// static Middleware, express.static()을 사용하여 정적 파일을 제공합니다.
// express.static() 함수는app.js 파일 기준으로,
// 입력 값(지금은 "./assets") 경로에 있는 파일을 아무런 가공 없이 그대로 전달해주는 미들웨어
app.use(express.static('./assets'));
이제 MongoDB 연결준비를 위해 mongoose를 사용하여, Mongoose의 공식 문서를 따라 schemas 폴더를 생성하고, 하위 파일인 index.js를 아래처럼 준비해보자.
// schemas/index.js import mongoose from 'mongoose'; const connect = () => { mongoose .connect( // 아래 부분은 대여한 ID, Password, 내 주소에 맞게끔 수정해주자! 'mongodb+srv://sparta-user:aaaa4321@express-mongo.uy7ttg7.mongodb.net/?retryWrites=true&w=majority', { dbName: 'todo_memo', // todo_memo 데이터베이스명을 사용. }, ) .then(() => console.log('MongoDB 연결에 성공하였습니다.')) .catch((err) => console.log(`MongoDB 연결에 실패하였습니다. ${err}`)); }; mongoose.connection.on('error', (err) => { console.error('MongoDB 연결 에러', err); }); export default connect;
import connect from './schemas/index.js';
connect();
MongoDB를 도입하기 위해 필요한 데이터와 형식을 정의하는 것은 스키마(Schema) 설계의 가장 중요한 요소이다. 그러므로 할 일 메모 사이트의 기능을 바탕으로 필요한 데이터를 설계해보자.
- 먼저 해야할일을 (Value) 라는 문자열(String)형식의 필드(Field)를 정의하자.
- 그 다음 해야 할 일의 순서(order)라는 숫자(Number) 형식의 필드(Field)를 정의한다.
- 마지막으로, 할 일을 완료했다면 언제 완료했는지 시간을 확인하는 완료 날짜(doneAt)라는 날짜(Date) 형식의 필드(Field)를 정의해보자.
todo.schema.js 라는 파일을 만들어준뒤 아래 형식대로 코드를 작성해준다.
할 일 등록 =>
POSTMethod (/api/todos/)
요청(request){ "value":"제로 콜라 500ml 구매하기" }응답(response)
{ "todo": { "value": "제로 콜라 500ml 구매하기", "order": 1, "_id": "64bd3e6a8f9c069e092ee5c4", "__v": 0, "todoId": "64bd3e6a8f9c069e092ee5c4", "id": "64bd3e6a8f9c069e092ee5c4" } }
할 일 목록 조회 =>
GETMethod (/api/todos/)
응답(response){ "todos": [ { "_id": "64bd3e6a8f9c069e092ee5c4", "value": "제로 콜라 500ml 구매하기", "order": 1, "__v": 0, "todoId": "64bd3e6a8f9c069e092ee5c4", "id": "64bd3e6a8f9c069e092ee5c4" } ] }
가장 먼저, 할 일 메모 사이트의 API를 구현하기 위해서는, 이전처럼 해야할 일의 기능을 사용하는 라우터(Router)를 생성하고, app.js 파일에서 해당하는 Router를 등록해야한다.
따라서 /routes/todos.router.js 파일을 생성한 후, 아래 코드를 입력해주자.

todos.router.js 파일을 추가하고 코드 작성이 완료되었다면, 이제는 app.js의 전역 미들웨어에 생성한 Router를 연결하기 위해 app.js파일에 아래 임포트 코드를 입력하자!
import TodosRouter from './routes/todos.router.js';
// /api 주소로 접근하였을 때, router와 TodosRouter로 클라이언트의 요청이 전달됩니다.
app.use('/api', [router, TodosRouter]);
이제 할 일(Todo) 모델을 구성했으니 이제 할 일 추가 API를 짜보자
우리는 위에서 할 일 스키마를 정의할 때 order라는 해야할 일 데이터가 생성될 때마다 순서를 가질 수 있게 만들어주는 필드를 정의해 주었다. 이에 따라 아래 처럼 할 일 추가 API를 구현해보자.

🤷♀️exec() 메서드는 왜 사용할까?
- ongoose에서 exec()는 결과를 반환하기 위해 쿼리를 실행하고, 이 결과로 Promise를 반환하게 된다.
- 만약, exec() 메서드를 사용하지 않는다면, 해당 쿼리는 결과값이 Promise로 반환되지 않기 때문에,
null로 정의될 수 있다.
데이터 유효성 검사(Validation)란, 전달받은 데이터가 예상한 형식과 일치하는지 확인하기 위한 작업을 뜻한다.
- 예상치 못한 에러를 위해 미리 데이터 유효성 검사 기능을 아래와 같은 코드로 추가해놔야 한다.
- 그 다음 테스트를 위해 insomnia 를 이용해 실험해보면 value 값을 잘 입력하면 추가가 되었고,
- value 값이 아무 값도 입력이 되지 않으면 에러가 발생했다는것을 확인할 수 있다.
- 위에서
POSTMethod를 통해 추가한 목록들을 조회 하기 위해 아래 코드를 routes/todos.router.js에 붙여주자
- 그럼 아래와 같이 새로 입력했던 정보들이 오름차순으로 나열된 모습을 확인할 수 있다 !
할 일 순서변경, 내용 변경, 완료/해제 =>
PATCHMethod (/api/todos/:todoId)
요청(request){ "order": 2, "value": "수정된 해야할 일입니다.", "done": false }응답(response)
{}
할 일 삭제 =>
DELETEMethod (/api/todos/:todoId)
요청(request){}응답(response)
{}
현재 프론트엔드에서는 PATCH /api/todos:/todoId API를 호출한 후, 바로 GET /api/todos API를 요청하도록 구현되었기 때문에 response 칸은 비워져있다.해야할 일 순서를 변경하는 것은 Todo 데이터에서 order 값만 변경해주면 된다. 여기서 주의해야할 점은 3번째 할 일을 2번으로 변경할 때, 만약 2번 할 일이 이미 존재한다면 3→2, 2→3 과 같이 2개의 Todo 데이터에서 order를 함께 변경해줘야 한다.
- 아래와 같이 할 일 순서 변경 API를 만들어보자
- 아래 11번째 내용을 14번째로 바꿔보자
- 바꾸면 아래와 같이 order 번호가 14로 바뀐것을 확인 할 수 있다.
할 일 완료/해제 API는 done이라는 값을 전달받아 할 일 완료/해제 기능을 구현할 수 있다.
- 할 일 완료/해제 API에서 가장 중요한 것은 API에서 Todo 항목의 doneAt이라는 필드에 완료된 시점의 시간을 기입해야 한다는 것이다.
const { order, done } = req.body; // done 추가
// 아래쪽에
if (done !== undefined) {
// 변경하려는 '해야할 일'의 doneAt 값을 변경합니다.
currentTodo.doneAt = done ? new Date() : null; // true 일시 현재시간을 넣어주고 false 면 null값을 넣어준다.
}
- 위의 코드를 추가 했으면 insomnia를 통해 아래와 같이 done 값을 true 로 입력해주면
- 아래와 같이 doneAt 값이 생기면서 현재 시간이 추가되면서 나오게 된다.
- 마찬가지로 done 의 값을 false로 입력해주면
- doneAt의 값이 null로 나오는 것을 확인 할 수 있었다.
API는 삭제할 할 일의 ID를 클라이언트에게 전달받아, MongoDB에서 해당 데이터를 삭제하도록 구현하게 된다.
// routes/todos.router.js
/** 할 일 삭제 **/
router.delete('/todos/:todoId', async (req, res) => {
// 삭제할 '해야할 일'의 ID 값을 가져옵니다.
const { todoId } = req.params;
// 삭제하려는 '해야할 일'을 가져옵니다. 만약, 해당 ID값을 가진 '해야할 일'이 없다면 에러를 발생시킵니다.
const todo = await Todo.findById(todoId).exec();
if (!todo) {
return res
.status(404)
.json({ errorMessage: '존재하지 않는 todo 데이터입니다.' });
}
// 조회된 '해야할 일'을 삭제합니다.
await Todo.deleteOne({ _id: todoId }).exec();
return res.status(200).json({});
});
- 위 코드가 입력이 되었으면 이번엔 할 일을 삭제할 시간이다.
- 위의 13번째 내용을 삭제하기위해 아래와 같이 13번째의 id 값을 입력해주고 send를 누르면
- 12번째 와 14번째 사이에 있어야 할 13번째 값이 삭제된 모습을 볼 수 있다.
const { order, done, value } = req.body;
if (value) {
// 변경하려는 '해야할 일'의 내용을 변경합니다.
currentTodo.value = value;
}
- 내용을 변경 하는 연습을 해보자. PATCH를 통해 아래와 같이 value 값을 입력 해준다.
- 그러면 14번째 value 값이 변경된 모습을 볼 수 있당.