라우팅
라우팅 (Routing)
라우팅은 애플리케이션의 엔드포인트(URI)가 클라이언트의 요청에 어떻게 응답할지를 정하는 규칙이에요. Express는 HTTP 메서드에 대응하는 app 객체의 메서드로 라우팅을 정의하는데, 예를 들어 GET 요청을 처리할 때는 app.get()을 사용하고 POST 요청은 app.post()로 처리해요. 이 라우팅 메서드들은 콜백 함수(핸들러)를 받아서, 요청이 라우트와 HTTP 메서드에 일치할 때 Express가 자동으로 실행해 줘요.
라우트 메서드
라우트 메서드는 HTTP 메서드 중 하나에서 유래하며 express 인스턴스에 붙어요. 앱 루트에 대해 GET과 POST 메서드의 라우트를 정의하는 예시를 볼게요.
// GET 메서드 라우트
app.get('/', (req, res) => {
res.send('GET request to the homepage');
});
// POST 메서드 라우트
app.post('/', (req, res) => {
res.send('POST request to the homepage');
});
Express는 get, post 등 모든 HTTP 요청 메서드에 대응하는 메서드를 지원해요. 특별히 app.all()이라는 메서드는 모든 HTTP 요청 메서드에 대해 해당 경로에서 미들웨어를 실행하는 데 사용돼요.
app.all('/secret', (req, res, next) => {
console.log('Accessing the secret section ...');
next(); // 다음 핸들러에 제어를 넘깁니다
});
라우트 경로
라우트 경로는 요청 메서드와 함께 요청이 이루어질 수 있는 엔드포인트를 정의해요. 문자열 또는 정규식으로 작성할 수 있고, 아래의 "라우트 매개변수"에서 설명하듯 URL에서 값을 꺼내올 수도 있지요. Express는 경로 매칭에 path-to-regexp v8을 사용해요.
문자열 경로는 요청을 정확히 일치시켜요. 점(.)과 하이픈(-)은 리터럴로 해석되고, 쿼리 문자열은 라우트 경로에 포함되지 않아요.
app.get('/', (req, res) => {
res.send('root');
});
app.get('/about', (req, res) => {
res.send('about');
});
app.get('/random.text', (req, res) => {
res.send('random.text');
});
여기서 주의할 점이 하나 있어요. ?, +, *, [], () 같은 문자는 예약어라서 라우트 경로의 리터럴 문자로 쓸 수 없어요. 필요하면 \로 이스케이프해야 해요.
정규식 경로는 더 복잡한 매칭 로직이 필요할 때 유용해요.
// "a"를 포함하는 모든 경로와 매칭
app.get(/a/, (req, res) => {
res.send('/a/');
});
// "fly"로 끝나는 모든 경로와 매칭 (butterfly, dragonfly 등)
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/');
});
라우트 매개변수
라우트 매개변수는 URL에서 특정 위치의 값을 캡처하는 이름 붙은 URL 세그먼트예요. 캡처된 값은 req.params 객체에 채워지고, 경로에 지정된 라우트 매개변수의 이름이 그 키가 돼요. 세 가지 형태가 있어요: 명명된 매개변수(:name), 와일드카드(*name), 그리고 이것들을 중괄호로 감싼 선택적 세그먼트.
// 경로: /users/:userId/books/:bookId
// 요청 URL: http://localhost:3000/users/34/books/8989
// req.params: { "userId": "34", "bookId": "8989" }
app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params);
});
하이픈과 점은 리터럴로 해석되므로 라우트 매개변수와 함께 유용하게 쓸 수 있어요.
// 경로: /flights/:from-:to
// 요청 URL: http://localhost:3000/flights/LAX-SFO
// req.params: { "from": "LAX", "to": "SFO" }
// 경로: /plantae/:genus.:species
// 요청 URL: http://localhost:3000/plantae/Prunus.persica
// req.params: { "genus": "Prunus", "species": "persica" }
와일드카드는 접두사 뒤의 모든 경로와 매칭돼요. 이름을 가져야 하지만 문자열 대신 경로 세그먼트의 배열로 캡처돼요.
app.get('/files/*filepath', (req, res) => {
// GET /files/images/logo.png
console.dir(req.params.filepath);
// => [ 'images', 'logo.png' ]
res.send(`File: ${req.params.filepath.join('/')}`);
});
중괄호를 사용하면 선택적 세그먼트를 정의할 수 있어요. 세그먼트가 없으면 해당 매개변수는 req.params에서 생략돼요.
app.get('/:file{.:ext}', (req, res) => {
// GET /image.png => req.params = { file: 'image', ext: 'png' }
// GET /image => req.params = { file: 'image' }
res.send('ok');
});
라우트 핸들러
여러 개의 콜백 함수를 제공해서 미들웨어처럼 요청을 처리할 수 있어요. 유일한 차이는 콜백이 next('route')를 호출해 남은 라우트 콜백을 건너뛸 수 있다는 점이에요. 이 메커니즘으로 라우트에 사전 조건을 걸고, 진행할 이유가 없으면 후속 라우트로 제어를 넘길 수 있어요.
app.get('/user/:id', (req, res, next) => {
if (req.params.id === '0') {
return next('route');
}
res.send(`User ${req.params.id}`);
});
app.get('/user/:id', (req, res) => {
res.send('Special handler for user ID 0');
});
라우트 핸들러는 단일 함수, 함수 배열, 또는 둘의 조합 형태로 작성할 수 있어요. 여러 콜백으로 처리할 때는 next 객체를 꼭 지정해야 한다는 점을 기억해 두세요.
app.get(
'/example/b',
(req, res, next) => {
console.log('the response will be sent by the next function ...');
next();
},
(req, res) => {
res.send('Hello from B!');
}
);
Response 메서드
res(response 객체)의 메서드는 클라이언트에 응답을 보내고 요청-응답 사이클을 종료해요. 라우트 핸들러에서 이 메서드 중 하나라도 호출하지 않으면 클라이언트 요청은 계속 대기 상태로 남아요.
app.route()
app.route()를 사용하면 하나의 라우트 경로에 대해 체이닝 가능한 라우트 핸들러를 만들 수 있어요. 경로가 한 곳에 지정되므로 모듈식 라우트를 만들고 중복과 오타를 줄이는 데 도움이 돼요.
app
.route('/book')
.get((req, res) => {
res.send('Get a random book');
})
.post((req, res) => {
res.send('Add a book');
})
.put((req, res) => {
res.send('Update the book');
});
express.Router
express.Router 클래스로 모듈식이고 마운트 가능한 라우트 핸들러를 만들 수 있어요. Router 인스턴스는 완전한 미들웨어이자 라우팅 시스템이라 "미니 앱"이라고도 불러요.
라우터 파일을 만들어 그 안에 미들웨어를 로드하고 라우트를 정의한 뒤, 메인 앱의 경로에 라우터 모듈을 마운트하는 방식이에요. 예를 들어 birds.js라는 라우터 파일을 만들고:
const express = require('express');
const router = express.Router();
// 이 라우터에만 적용되는 미들웨어
const timeLog = (req, res, next) => {
console.log('Time: ', Date.now());
next();
};
router.use(timeLog);
// 홈 페이지 라우트 정의
router.get('/', (req, res) => {
res.send('Birds home page');
});
// about 라우트 정의
router.get('/about', (req, res) => {
res.send('About birds');
});
module.exports = router;
앱에서 라우터 모듈을 로드합니다:
const birds = require('./birds');
// ...
app.use('/birds', birds);
이제 앱은 /birds, /birds/about 요청을 처리하고, 그 라우트에 특화된 timeLog 미들웨어도 호출할 수 있어요. 부모 라우트 /birds에 경로 매개변수가 있고 서브 라우트에서 그 값을 접근해야 한다면, mergeParams 옵션을 Router 생성자에 전달해야 해요.
const router = express.Router({ mergeParams: true });