미들웨어는 서로 다른 애플리케이션이 서로 통신하는 데 사용되는 sw로 애플리케이션을 지능적이고 효율적으로 연결하는 기능을 제공하여 요청을 받을 때 요청에 대한 공통적인 처리를 담당함.
미들웨어는 다양한 기술, 도구, 데이터베이스 간에 교량 역할로 단일 시스템에 통합되도록 하여 유저에게 통합된 서비스를 제공한다.
보통의 예시로 본다면, 로깅이나 인증과 관련하여 미들웨어를 사용하는 것을 생각해 볼 수 있다.
예전 인턴할 때 .NET 서버에서 Serilog를 이용한 적이 기억난다.
app.use((req, res, next) => {
// req: request 객체
// res: response 객체
// next: 다음 스택으로 정의된 다음 미들웨어 호출
});
https://expressjs.com/ko/4x/api.html
위 링크를 통해 기본 미들웨어 들을 살펴볼 수 있다.
만약 세가지 미들웨어를 적용한다고 하면,
app.use((req, res, next) => {
/* 첫번째 미들웨어 로직 */
next();
});
app.use((req, res, next) => {
/* 두번째 미들웨어 로직 */
if ()
{
/* flow 1 */
}
else
{
/* flow2 */
res.send();
}
next();
});
app.use((req, res, next) => {
/* 세번째 미들웨어 로직 */
next();
});
request에 대해 미들웨어 처리 과정을 거쳐 response 전달 과정이 위와 같기 때문에, next()를 호출하여 넘어가지만, 현재 미들웨어에서 send()나 json() 메서드를 통해 응답을 바로 보낼 경우에는 next()를 호출해선 안된다. (중복된 요청 문제)
안정적인 서버 흐름 제어를 위해 에러를 발생시켜 이에 대한 제어를 담당할 미들웨어가 필요하다.
(Nest.js에서 Exception Filter)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('[Error] error occurred');
});
// err: 이전 미들웨어에서 발생한 오류 객체
Experss에서는 미들웨어나 라우터에서 에러가 발생하면, 해당 에러를 next 함수를 통해 다음 미들웨어로 전달하고, 에러 처리 미들웨어를 찾아 실행한다.
미들웨어는 등록된 순서대로 실행되기에, 비즈니스 로직을 수행한 후 발생한 에러는 다음 미들웨어로 전달되기 때문에 라우터 이전에 에러 처리 미들웨어를 등록하면 라우터에서 발생한 에러를 처리할 수 없습니다.
따라서, 항상 에러 처리 미들웨어는 라우터 설정 코드 하단에 위치해야 한다.
Joi는 js에 여러 타입과 규칙을 통해 유효성 검증 기능을 가진 라이브러리이다.
(Nest.js에서는 pipe라고 불리기도 함)
https://joi.dev/api/?v=17.13.3 링크에서 joi 문법과 관련한 설명을 볼 수 있다.
아래 코드 스니펫은 위 링크에 필자가 일부 주석을 달아놓으 ㄴ것이다.
const Joi = require('joi');
// 스키마 정의
const schema = Joi.object({
// min, max로 형식을 제한한 문자열 username
username: Joi.string()
.alphanum()
.min(3)
.max(30)
.required(),
// regex 정규표현식으로 password 형식 패턴 정의
password: Joi.string()
.pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')),
repeat_password: Joi.ref('password'),
access_token: [
Joi.string(),
Joi.number()
],
birth_year: Joi.number()
.integer()
.min(1900)
.max(2013),
email: Joi.string()
.email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } })
})
.with('username', 'birth_year')
.xor('password', 'access_token')
.with('password', 'repeat_password');
schema.validate({ username: 'abc', birth_year: 1994 });
// -> { value: { username: 'abc', birth_year: 1994 } }
schema.validate({});
// -> { value: {}, error: '"username" is required' }
// Also -
// try catch 통해 검증 결과: 형식과 일치하지 않으면 오류 반환
try {
const value = await schema.validateAsync({ username: 'abc', birth_year: 1994 });
}
catch (err) { }