라우팅
express 공식 문서 에서는 라우팅을 아래와 같이 설명하고 있다.
**라우팅**은 URI(또는 경로) 및 특정한 HTTP 요청 메소드(GET, POST 등)인 특정 엔드포인트에 대한 클라이언트 요청에 애플리케이션이 응답하는 방법을 결정하는 것을 말합니다.
정리하자면 요청 URI 규칙에 맞게 특정한 요청이 오면, 이에 맞는 응답 핸들러를 결정하는 것이라 생각하면된다
기본 문법
기본 문법은 다음과 같다. API 메서드(Http Method, string
)는 HTTP 메서드를 작성한다. (GET, POST, PUT, DELETE..) 첫 번째 인자는 요청 URI 경로, 두 번째 인자에는 응답 핸들러(Handler, function
)를 받는다.
const express = require('express');
const app = express();
app.get('/', function (req, res) {
res.send('hello world!');
});
Request
핸들러 내부 인수 req
는 요청 정보 값을 담고 있다.
-
req.params
object
: 요청 파라미터 만약 클라이언트에서 /page/admin 같이 여러 페이지를 이동하려는 api를 호출한다고 치면 페이지 마다 라우팅을 작성하는 것은 비효율 적이다. 따라서 아래와 같이 동적으로 URI 파라미터를 가져와 분기시키는 것이 낫다.//request api : http://localhost:8085/page/home app.get('/page/:name', (req,res) => { const pageName = req.params.name; res.sendFile(path.join(__dirname, `/public/${pageName}.html`); });
-
req.query
object
: 요청 쿼리스트링 조회성 api를 요청할 때 서버에서는req.query.key
형태로 값을 검증할 수 있다.//request api : http://localhost:8085/user/?name=jiny&id=admin app.get('/user', (req, res) => { const resJson = {}; if (req.query.name && req.query.id) { resJson.success = true; resJson.message = 'success'; } else { resJson.success = false; resJson.message = 'invalid'; } res.json(resJson); });
Response
핸들러 내부 인수로 사용되는 res
메서드는 아래와 같은 종류로 나눠진다.
- res.download() : 파일 다운로드 프롬프트
- res.status() : HTTP 상태 코드 지정
- res.end() : 응답 프로세스 종료
- res.json() : JSON 응답
- res.redirect() : 요청 경로를 다시 설정
- res.send() : 여러 유형으로 응답을 전송한다. (자동으로
Content-Type
헤더가 지정된다.) - res.sendFile() : 파일을 전송한다. (주로 HTML과 같은 정적 파일을 전송하는 데 사용)
- res.sendStatus() : HTTP 상태 코드를 전송한다.
Handler
라우팅 핸들러도 미들웨어와 비슷한 방식으로 여러 콜백 함수로 처리가 가능하다.
위의 Request 예제에서 페이지 라우팅 코드를 보면 사용자가 만약 존재하지 않는 페이지를 요청한다면 당연히 파일을 탐색하지 못하니까 에러를 출력하게된다. 이런 상황에 대비해 핸들러를 추가하여 사전에 방지를 할 수 있다.
const redirect404 = (req, res) => {
res.status(404).send('Bad Request.');
};
//정의한 페이지가 아니라면 'Bad Request.'라는 메시지를 전송한다.
app.get(
'/page/:name',
(req, res, next) => {
const pageName = req.params.name;
const validPath = ['about', 'admin', 'main'];
if (validPath.includes(pageName)) {
res.sendFile(path.join(__dirname, `/public/${pageName}.html`));
} else {
next();
}
},
redirect404
);
모듈화
규모가 커질수록 모듈화는 항상 고민해야한다. 모든 요청 라우팅을 한 스크립트에 작성하면 꽤나 복잡하고 관리에 힘들기 때문에 적절한 모듈화가 필요하다.
모듈 작성
어떻게 모듈화를 할지는 개발자 나름이다. 위의 예제로 연습삼아 사용한 3개의 라우팅을 각각 모듈화를 해보면 ( URI 상위 계층끼리 모듈화를 하였다.)
- index : 최상위 Route
- page : 페이지 관련 Route
- user : 사용자 관련 Route
🗂 project root
├── app.js
├── node_modules
├── package-lock.json
├── package.json
├── public
│ ├── about.html
│ ├── admin.html
│ └── main.html
└── routes
├── index.js
├── page.js
└── user.js
아마 npm 패키지가 설치된 프로젝트 구조는 위와 같이 생겼을 것이다. 여기에 routes
라는 디렉토리를 생성했다. 모듈화를 진행 할 모든 라우팅 구조는 아래와 같다.
/* 📄routes/index.js */
//express 모듈 호출
const express = require('express');
//경로 재 정의를 위한 path 모듈 호출
const path = require('path');
//라우팅 모듈 호출
const router = express.Router();
//라우팅 모듈화
router.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '../public/main.html'));
});
//export
module.exports = router;
모듈 적용하기
미들웨어로 분리된 라우팅 파일을 읽어오면 사용할 수 있다. 모든 라우팅은 Router
모듈에 등록되어있기 때문이다.
//📄app.js
const express = require('express');
const app = express();
const indexRouter = require('./routes');
const userRouter = require('./routes/user');
const pageRouter = require('./routes/page');
app.set('port', 8085);
app.use('/', indexRouter);
app.use('/user', userRouter);
app.use('/page', pageRouter);
app.listen(app.get('port'), () => {
console.log('start express server');
});
주의사항
주의 사항으로는 실제 요청을 호출하면 주소가 겹치기 때문에 요청 주소를 알맞게 작성해야한다. 만약 원래 /page/:name
이라는 요청 경로를 작성했다면 최상위 경로인 /page
를 제외하고 :/name
으로 다시 분리해야한다.
//📄app.js
app.use('/page', pageRouter);
//📄routes/page.js
router.get('/:name', (req, res, next) => {
const pageName = req.params.name;
const validPath = ['about', 'admin', 'main'];
if (validPath.includes(pageName)) {
res.sendFile(path.join(__dirname, `../public/${pageName}.html`));
} else {
next();
}
});
참고자료
- Node.js 교과서 - Router 객체로 라우팅 분리하기 (조현영 저)
- Express 공식문서 - 라우팅