req: 요청(Request)에 대한 정보가 담겨있는 객체입니다.res: 응답(Response)을 위한 기능이 제공됩니다.next: 다음 스택으로 정의된 미들웨어를 호출합니다.app.use((req, res, next) => {
console.log('두번째 미들웨어');
next();
});
app.use((req, res, next) => {
console.log('세번째 미들웨어');
next();
});
// print: 첫번째 미들웨어
// print: 두번째 미들웨어
// print: 세번째 미들웨어
next() 가 실행되지 않으면 다음 미들웨어는 실행되지 않고, 클라이언트의 요청은 거기서 종료됩니다.res.send()이나 res.json()등의 메서드를 호출하는 경우에는 next() 를 호출하면 안됩니다. 이렇게 하지 않으면 이미 요청이 종료된 상태에서 다른 미들웨어가 응답을 보내려고 하여 중복된 요청이 전달되는 문제가 발생하게됩니다.app.use(Middleware) : 모든 요청에서 미들웨어가 실행된다.app.use(’/api’, Middleware) : /api로 시작하는 모든 요청에서 미들웨어를 실행한다.app.post(’/api’, Middleware, (req,res,)=>{} ) : /api로 시작하는 POST 요청에서 미들웨어를 실행한다.// Joi 스키마를 정의합니다.
const schema = Joi.object({
// name Key는 문자열 타입이고, 필수로 존재해야합니다.
// 문자열은 최소 3글자, 최대 30글자로 정의합니다.
name: Joi.string().min(3).max(30).required(),
});
// 검증할 데이터를 정의합니다.
const user = { name: 'Foo Bar' };
// schema를 이용해 user 데이터를 검증합니다.
const validation = schema.validate(user);
// 검증 결과값 중 error가 존재한다면 에러 메시지를 출력합니다.
if (validation.error) {
console.log(validation.error.message);
} else {
// 검증 결과값 중 error가 존재하지 않는다면, 데이터가 유효하다는 메시지를 출력합니다.
console.log('Valid Data!');
}
이메일 검증하기
import Joi from 'joi';
// Joi 스키마를 정의합니다.
const schema = Joi.object({
// name Key는 문자열 타입이고, 필수로 존재해야합니다.
// 문자열은 이메일 형식에 맞아야합니다.
email: Joi.string().email().required(),
});
// 검증할 데이터를 정의합니다.
const user = { email: 'foo@example.com' };
// schema를 이용해 user 데이터를 검증합니다.
const validation = schema.validate(user);
// 검증 결과값 중 error가 존재한다면 에러 메시지를 출력합니다.
if (validation.error) {
console.log(validation.error.message);
} else {
// 검증 결과값 중 error가 존재하지 않는다면, 데이터가 유효하다는 메시지를 출력합니다.
console.log('Valid Email User!');
}
5) Joi를 이용한 Validation 처리 (비동기)
import Joi from 'joi';
// Joi 스키마를 정의합니다.
const schema = Joi.object({
// name Key는 문자열 타입이고, 필수로 존재해야합니다.
// 문자열은 최소 3글자, 최대 30글자로 정의합니다.
name: Joi.string().min(3).max(30).required(),
});
// 검증할 데이터를 정의합니다.
const user = { name: 'Foo Bar' };
try {
// schema를 이용해 user 데이터를 검증합니다.
const validation = await schema.validateAsync(user);
// 검증 결과값 중 error가 존재하지 않는다면, 데이터가 유효하다는 메시지를 출력합니다.
console.log('Valid Data!');
} catch (error) {
// 검증에 실패한다면, 에러 메시지를 출력합니다.
console.log(error.message);
}
6) 할 일 생성 API 리팩토링하기
// routes/todos.router.js
import Joi from 'joi';
// 할 일 생성 API의 요청 데이터 검증을 위한 Joi 스키마를 정의합니다.
const createTodoSchema = Joi.object({
value: Joi.string().min(1).max(50).required(),
});
router.post('/todos', async (req, res) => {
// 클라이언트에게 전달받은 데이터를 검증합니다.
const validation = await createTodoSchema.validateAsync(req.body);
// 클라이언트에게 전달받은 value 데이터를 변수에 저장합니다.
const { value } = validation;
// Todo모델을 사용해, MongoDB에서 'order' 값이 가장 높은 '해야할 일'을 찾습니다.
const todoMaxOrder = await Todo.findOne().sort('-order').exec();
// 'order' 값이 가장 높은 도큐멘트의 1을 추가하거나 없다면, 1을 할당합니다.
const order = todoMaxOrder ? todoMaxOrder.order + 1 : 1;
// Todo모델을 이용해, 새로운 '해야할 일'을 생성합니다.
const todo = new Todo({ value, order });
// 생성한 '해야할 일'을 MongoDB에 저장합니다.
await todo.save();
res.status(201).json({ todo });
});
7) try/catch 설정하기
// /routes/todos.router.js
/ 할 일 생성 API 리팩토링, 에러 처리 /
router.post('/todos', async (req, res) => {
try {
// 클라이언트에게 전달받은 데이터를 검증합니다.
const validateBody = await createTodoSchema.validateAsync(req.body);
// 클라이언트에게 전달받은 value 데이터를 변수에 저장합니다.
const { value } = validateBody;
// Todo모델을 사용해, MongoDB에서 'order' 값이 가장 높은 '해야할 일'을 찾습니다.
const todoMaxOrder = await Todo.findOne().sort('-order').exec();
// 'order' 값이 가장 높은 도큐멘트의 1을 추가하거나 없다면, 1을 할당합니다.
const order = todoMaxOrder ? todoMaxOrder.order + 1 : 1;
// Todo모델을 이용해, 새로운 '해야할 일'을 생성합니다.
const todo = new Todo({ value, order });
// 생성한 '해야할 일'을 MongoDB에 저장합니다.
await todo.save();
return res.status(201).json({ todo });
} catch (error) {
console.error(error);
// Joi 검증에서 에러가 발생하면, 클라이언트에게 에러 메시지를 전달합니다.
if (error.name === 'ValidationError') {
return res.status(400).json({ errorMessage: error.message });
}
// 그 외의 에러가 발생하면, 서버 에러로 처리합니다.
return res
.status(500)
.json({ errorMessage: '서버에서 에러가 발생하였습니다.' });
}
});
export default function (err, req, res, next) {
console.error(err);
// Joi 검증에서 에러가 발생하면, 클라이언트에게 에러 메시지를 전달합니다.
if (err.name === 'ValidationError') {
return res.status(400).json({ errorMessage: err.message });
}
// 그 외의 에러가 발생하면, 서버 에러로 처리합니다.
return res
.status(500)
.json({ errorMessage: '서버에서 에러가 발생하였습니다.' });
}
4) 에러 처리 미들웨어 등록하기
// app.js
import ErrorHandlerMiddleware from './middlewares/error-handler.middleware.js';
...
// /api 주소로 접근하였을 때, router와 TodosRouter로 클라이언트의 요청이 전달됩니다.
app.use('/api', [router, TodosRouter]);
// 에러 핸들링 미들웨어를 등록합니다.
app.use(ErrorHandlerMiddleware);
<aside>
💡 미들웨어는 **등록된 순서대로 실행**됩니다. **TodoRouter**에서 비즈니스 로직을 수행한 후 발생한 에러는 **다음 미들웨어로 전달**됩니다. 이 때, 에러 처리 미들웨어가 라우터 이후에 등록되어 있으면, 에러를 잡아 처리할 수 있게 되는것이죠.
반대로, **라우터 이전에 에러 처리 미들웨어를 등록**하면 라우터에서 발생한 에러를 처리할 수 없습니다.
왜냐하면, **라우터에서 발생한 에러는 라우터 이후에 등록된 미들웨어로 전달**되기 때문입니다. 이렇게 되면 에러 처리 미들웨어가 에러를 잡지 못하게 되는것이죠. 따라서, **항상 에러 처리 미들웨어는 라우터 설정 코드 하단**에 위치해야 합니다.5) 할 일 등록 API 리팩토링/ 에러 핸들러 /
router.post('/todos', async (req, res, next) => {
try {
// 클라이언트에게 전달받은 데이터를 검증합니다.
const validateBody = await createTodoSchema.validateAsync(req.body);
// 클라이언트에게 전달받은 value 데이터를 변수에 저장합니다.
const { value } = validateBody;
// Todo모델을 사용해, MongoDB에서 'order' 값이 가장 높은 '해야할 일'을 찾습니다.
const todoMaxOrder = await Todo.findOne().sort('-order').exec();
// 'order' 값이 가장 높은 도큐멘트의 1을 추가하거나 없다면, 1을 할당합니다.
const order = todoMaxOrder ? todoMaxOrder.order + 1 : 1;
// Todo모델을 이용해, 새로운 '해야할 일'을 생성합니다.
const todo = new Todo({ value, order });
// 생성한 '해야할 일'을 MongoDB에 저장합니다.
await todo.save();
return res.status(201).json({ todo });
} catch (error) {
// 발생한 에러를 다음 에러 처리 미들웨어로 전달합니다.
next(error);
}
});
6) 폴더 구조 살펴보기
todo-list
├── app.js
├── assets
│ ├── asset-manifest.json
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ ├── robots.txt
│ └── static
├── middlewares
│ └── error-handler.middleware.js
├── package.json
├── routes
│ └── todos.router.js
├── schemas
│ ├── index.js
│ └── todo.schema.js
├── todo-list-static-files.zip
└── yarn.lock
app.jsmongoose의 스키마(Schema) 및 모델(Model)을 정의하기 위해 사용합니다.