-
[Express.js] 미들웨어(Middleware)Back-End/Express.js 2021. 2. 15. 01:17반응형
마지막으로, Express의 기본개념인 미들웨어에 대해 공부한 내용을 정리하려고 한다.
프로젝트 서버를 구축하기 위해 Express를 공부하면서, 미들웨어라는 단어를 접할수록 혼란스러웠다.
Router에서 app.use() 내 함수도 미들웨어, body-parser 나 cors 들도 미들웨어(Third-Party) 라고 명명하는 것이었다.
애써 무시하고 기능들을 구현했었지만, Express 기본학습을 마무리하기 전에 미들웨어의 개념을 한 번 정리할 필요성을 느꼈다.
📗 미들웨어(Middleware) 란?
Express 공식문서에서는 미들웨어를 아래와 같이 정의한다.
Express는 자체적인 최소한의 기능을 갖춘 라우팅 및 미들웨어 웹 프레임워크이며, Express 애플리케이션은 기본적으로 일련의 미들웨어 함수 호출입니다.
미들웨어 함수는 요청 오브젝트(req), 응답 오브젝트 (res), 그리고 애플리케이션의 요청-응답 주기 중 그 다음의 미들웨어 함수 대한 액세스 권한을 갖는 함수입니다. 그 다음의 미들웨어 함수는 일반적으로 next라는 이름의 변수로 표시됩니다.
즉, 클라이언트에서 Request가 들어오고, 이를 Response로 반환하기 까지 사이에 거치는 모든 함수를 미들웨어라 하는 것이었다!
// app.js app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use(cors()); app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/rests', restsRouter);
내 프로젝트의 app.js 파일의 한 부분이다. 위쪽의 기본구성 및 cors() 는 express에서 제공하거나 npm에서 설치한 미들웨어들이다.
아래의 usersRouter, restsRouter 역시 미들웨어이지만, 이는 첫 번째 인자에서 알 수 있듯 특정 path에서만 작동되는 것이다.
- 미들웨어 문법
HTTP 메서드
미들웨어가 작동할 path
미들웨어 함수
다음 미들웨어로의 액세스 함수
HTTP response 객체
HTTP request 객체
위는, 내가 프로젝트 중 Router 모듈에서 많이 사용한 미들웨어 함수 호출을 위한 요소들이다.
이전 Router에서 공부했듯, app(express)의 HTTP 메서드들은 첫 번째 인자로 path값을, 두 번째 인자로 미들웨어 함수를 받는 것이다.
* next() 함수에 대해
next() 는, 현재 미들웨어에서 처리한 요청을 다음 미들웨어로 넘기기 위해 사용되는 메서드이다. 다음 예제로 알아보자
var express = require('express'); var app = express(); var requestTime = function (req, res, next) { req.requestTime = Date.now(); next(); }; app.use(requestTime); app.get('/', function (req, res) { var responseText = 'Hello World!'; responseText += 'Requested at: ' + req.requestTime + ''; res.send(responseText); });
- requestTime 이라는 미들웨어가 있다. Request 객체에, requestTime key와, 값으로는 현재 시간을 할당한다.
- 이 미들웨어에는 next() 메서드로 종료된다. 현재 메서드에서 가공된 요청(requestTime)이 다음 미들웨어로 넘어간다.
- app.get() 메서드 내 함수도 미들웨어이다. 여기서는 responseText 문자열에 Request로 수정해서 Response로 반환한다.
- 이 때, 이전 미들웨어에서 넘어온 requestTime을 받을 수 있기 때문에, 타임스탬프가 추가된 문자열이 반환될 것이다.
이 next() 함수로 인해 현재의 요청을 다음 미들웨어로 넘길 수 있다. (미들웨어 간 배치나 미들웨어 내 next() 위치가 중요!)
이를 공부하니, 프로젝트 초반 Router() 미들웨어 함수들에 next() 를 넣지 않아서 Response가 넘어오지 않았던 것이 문득 생각났다.
* 미들웨어(Middleware) vs 모듈(Module) ?
미들웨어는 공부했듯이, 요청과 응답 사이에서 중간처리를 하는 모든 함수들을 미들웨어라고 칭하는 것을 알 수가 있었다.
모듈은, 이러한 함수들의 묶음이라고 생각하면 된다. 기본적으로 제공되거나 혹은 필요에 의해 추가된 함수들의 집합체인 것이다.
- 내장 모듈: Node.js 에서 기본적으로 지원하는 모듈들
- 확장 모듈: Node.js 사용자들이 필요에 의해 만들어낸 오픈소스 기반의 모듈들
- 미들웨어의 역할
- 모든 코드를 실행 : Request와 Response 사이에 미들웨어 함수 내 코드들이 실행된다.
- 다음 미들웨어 호출 : next() 메서드를 통해 현재 미들웨어를 종료, 다음 미들웨어로 요청이 넘어간다.
- res, req 객체 변경 가능 : 미들웨어는 (req, res) 를 인자로 받는다. 이를 통해, request 수정이나 response 작성 등이 가능하다.
- 요청-응답 주기를 종료 : Response 메서드를 통해 미들웨어의 실행을 종료시킬 수 있다. (res.send, res.json..)
📗 미들웨어(Middleware) 유형
Express 에서는 다음과 같이 크게 4가지 타입의 미들웨어를 구분한다. (미들웨어가 다뤄지는 영역이나 레벨을 기준으로 나눈 것이다.)
- 어플리케이션 레벨 미들웨어
- 라우터 레벨 미들웨어
- 오류 처리 미들웨어
- 써드파티 미들웨어
1. 어플리케이션 레벨 미들웨어
app.use() 나 app.METHOD() 의 함수를 이용해 미들웨어를 app객체 인스턴스에 바인드 시킨다. (METHOD = get, post..)
// 예제1 app.use(function (req, res, next) { console.log('Time:', Date.now()); next(); }); // 예제2 app.get('/user/:id', function (req, res, next) { res.send('USER'); });
예제1처럼 path가 없다면 모든 요청에서, 예제2는 해당 path로 들어온 요청에 대해서 미들웨어를 실행할 수 있다.
미들웨어는 위처럼 하나가 아니라 여러개를 연결할 수 있고, 이를 조건부로 이동할 수 있다. 아래 예제를 보자.
app.get('/user/:id', function (req, res, next) { if (req.params.id == 0) { next('route'); } else next(); }, function (req, res, next) { res.render('regular'); }); app.get('/user/:id', function (req, res, next) { res.render('special'); });
- 첫 번째 app.get() 의 미들웨어가 실행된다. path parameter가 0이면 next('route'), 아니라면 next() 가 실행된다.
- next('route') 는 next() 의 문법이며, 현재 미들웨어의 남은 스택들을 종료하고 다음 라우트로 넘어가는 메서드다.
- 즉, 현재 미들웨어에 남은 렌더는 실행되지 않고, 다음 라우트의 res.render('special') 이 실행될 것이다.
- next()의 경우 다음 미들웨어 스택인 res.render('regular') 가 실행될 것이다.
2. 라우터 레벨 미들웨어
express.Router() 객체의 인스턴스에 바인드된다는 차이점을 제외하고, 어플리케이션 레벨과 동일하게 작동한다.
마찬가지로, router.use() 혹은 router.METHOD() 함수 내에서 로드할 수 있다.
var router = express.Router(); router.use(function (req, res, next) { console.log('Time:', Date.now()); next(); });
Router 객체는 반대로 그 자체도 미들웨어로 작동하기 때문에, app.use() 나 다른 router.use() 에서 사용될 수 있다.
또한, Router 객체로 생성된 라우트 미들웨어는 app.use() 를 사용해 마운트 시켜야지만 사용할 수 있다.
// app.js ... var usersRouter = require('./routes/users'); ... app.use('/users', usersRouter); // users.js router.post('/signin', (req, res, next) => { try { const response = checkSignIn(req, usersDB); return res.json(response); } catch(err) { throw new Error(err); } next(); }) module.exports = router;
이처럼, app.js 에선 모듈화된 users 라우트를 usersRouter 변수에 담는다. 또한, 이를 app.use() 메서드의 특정경로에서 실행한다.
users.js 는 라우트 레벨 미들웨어들을 가지고 있으며, 이들을 마지막에 module.exports 해줘야 앱에서 활용가능하다.
이렇게, 라우트를 따로 분리하는 장점은, 특정 root URL 기점으로 기능이나 로직별로 라우팅을 나눠서 관리할 수 있기 때문이다.
(위 예시는, /users/signin 에서 실행되는 것이다. /users 경로 기준으로, 다양한 기능의 라우팅을 추가할 수 있음)
3. 에러 핸들링 미들웨어
말 그대로 오류 처리를 위한 미들웨어이다. 특별한 점은, (err, req, res, next) 네 개의 인자를 받아야 한다는 점이다.
next() 메서드가 필히 사용되진 않지만, 시그니처를 유지하기 위해 포함해야 하고 없다면 에러 핸들링 미들웨어로 인식되지 않는다.
// catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404)); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); });
express-generator 로 초기세팅을 했을때, app.js 맨 마지막에 작성된 이 부분이 에러 핸들링 미들웨어였던 것이다.
4. 서드 파티 미들웨어
Express 에 기능을 추가하기 위해 설치하는 미들웨어들이다.
필요한 모듈은 Node.js 에서 설치하고, 애플리케이션 혹은 라우터 레벨에 포함시키면 된다. (아래 예제는 각각 app, router)
// npm npm i cors // express.js const cors = require('cors'); app.use(cors());
// npm npm i bcrypt // express.js const bcrypt = require('bcrypt'); router.post('/signup', async (req, res, next) => { try { if { ... } else { response = successMessage(); const hashedPassword = await bcrypt.hash(body.password, 10); usersDB.push({ id: Date.now().toString(), ...body, password: hashedPassword, }) } ... }
정말 답답했던 혈이 뚫리는 기분이다!! 😃😃😃
미들웨어라는 말이 메서드같기도 하고, 라이브러리 같기도 해서 정~말 모호하던 참이었다. (결론은 둘 다 맞았던 것)
한편으론, 이를 확실히 공부하고 라우트를 작성했다면 좀 더 깔끔한 코드가 되지 않았을까 하는 아쉬움도 남는다. (리팩토링 고고!!)
아직 Express 기본에 관해 포스팅할 내용들이 많이 남았다. (공식문서에 있는 오류처리나, 4버전 / 5버전에 대한 내용들)
3차 프로젝트에 돌입하면, 지금까지 공부한 내용들과 앞으로의 학습내용들을 종합해서 좀 더 나은 서버를 만들어봐야겠다.
[출처]
- express.js 공식문서: 미들웨어 작성 - expressjs.com/ko/guide/writing-middleware.html
미들웨어 사용 - expressjs.com/ko/guide/using-middleware.html
- 라봉이 님의 블로그: psyhm.tistory.com/8?category=654716
- jinbroing 님의 블로그: jinbroing.tistory.com/126
반응형'Back-End > Express.js' 카테고리의 다른 글
[Express.js] CORS(Cross-Origin Resource Sharing) (0) 2021.02.13 [Express.js] Query String, Semantic URL (0) 2021.02.07 [Express.js] 인증/인가(Bcrypt, JWT) (0) 2021.02.04 [Express.js] Routing(express.Router()) (0) 2021.02.02 [Express.js] Express 설치 및 실행 (0) 2021.02.01