Express 라우팅은 HTTP 메서드와 경로를 핸들러 함수에 연결합니다. app.get('/users', handler)는 /users로 오는 GET 요청에 응답하고, app.post(), app.put(), app.delete()는 다른 메서드에 대해 같은 일을 합니다. /users/:id처럼 콜론으로 시작하는 세그먼트는 라우트 매개변수이고, 그 값은 req.params에 들어갑니다. 커지는 앱을 정리해 두려면 관련 라우트를 express.Router()에 넣고 app.use('/api', router)로 접두사 아래에 마운트하세요.
GET /users/2는 {"id":2,"name":"Linus"}로 응답합니다. 라우트 매개변수는 항상 문자열이라서 핸들러가 Number(req.params.id)로 비교합니다. 본문 {"name": "Grace"}로 POST /users를 보내면 201을 받고, 이어서 DELETE /users/1과 GET /users를 보내면 목록이 바뀌는 것을 볼 수 있습니다.
접두사를 붙인 express.Router()
라우터는 자신만의 라우트와 미들웨어를 가진 작은 앱입니다. 경로는 상대적이어서, /api/books에 마운트한 라우터 안에서 /:id로 쓴 라우트는 /api/books/:id에 응답합니다. 실제 프로젝트에서는 라우터마다 자기 파일에 두고 마지막에 module.exports = router를 씁니다.
GET /api/books/1은 {"id":1,"title":"Dune"}을 반환합니다. router.route('/:id')는 경로를 한 번만 쓰고 메서드마다 핸들러를 체인으로 잇습니다. PUT /api/books/1을 보내면 {"updated":"1"}을 받습니다. 마운트 경로를 /v2/books로 바꾸면 같은 라우터가 대신 그곳에서 응답합니다.
라우트 순서: 처음 일치한 것이 이깁니다
Express는 등록한 순서대로 라우트를 시도하고, 응답을 보낸 첫 라우트에서 멈춥니다. 매개변수 라우트는 그 자리에 오는 무엇이든 잡기 때문에, 그 뒤에 등록한 고정 경로에는 절대 도달하지 못합니다.
GET /users/new는 {"route":"/users/:id","id":"new"}로 응답합니다. 매개변수 라우트가 먼저 등록되어 그것을 가져갔기 때문입니다. /users/new 라우트를 /users/:id 위로 옮기고 다시 실행하면 {"route":"/users/new"}를 받습니다. 이어서 GET /books를 보내면 맨 아래의 404 핸들러에 도달합니다.
404 핸들러
Express는 일치하는 라우트가 없는 요청에 Cannot GET /books라는 자체 HTML 페이지로 응답합니다. API라면 보통 JSON을 원하므로, 위 블록처럼 경로 없는 app.use()를 맨 마지막 라우트로 추가하세요. 그 앞의 모든 라우트가 요청을 넘겼을 때만 실행됩니다. 핸들러가 던진 에러는 다른 경우로, 인자가 네 개인 전용 핸들러를 쓰며 Express 에러 처리에서 다룹니다.
Express 5의 라우트 경로
Express 5에서 경로 문법이 바뀌었는데, 온라인 예제 대부분은 Express 4 기준으로 쓰여 있습니다.
| 패턴 | Express 4 | Express 5 |
|---|---|---|
| 모든 경로 | app.get('*', fn) | app.get('/*splat', fn) |
| 선택적 매개변수 | /users/:id? | /users{/:id} |
| 매개변수 패턴 | /users/:id(\\d+) | 지원 안 함. 핸들러에서 값 확인 |
*만 쓰거나 매개변수 뒤에 ?를 붙이면 라우트를 등록할 때 TypeError가 발생해서 앱이 시작되지 않습니다.
흔한 실수
404 핸들러를 먼저 등록하는 것. 경로 없는 app.use()는 모든 것과 일치하므로 모든 요청이 404를 받습니다. 맨 아래에 두세요.
접두사가 제거된다는 걸 잊는 것. /api에 마운트한 라우터 안에서 req.path에는 /api가 없습니다. 전체 경로가 필요하면 req.originalUrl을 쓰세요.
매개변수를 숫자와 비교하는 것. req.params.id는 문자열 '1'이므로 req.params.id === 1은 항상 false입니다.
자주 묻는 질문
app.use와 app.get의 차이는 뭔가요?
app.get('/users', fn)은 경로가 정확히 /users인 GET 요청에서만 fn을 실행합니다. app.use('/users', fn)은 모든 메서드에서, /users로 시작하는 모든 경로에 실행됩니다. 그래서 라우터와 미들웨어를 마운트할 때 씁니다.
Express 라우트가 대소문자를 구분하게 하려면?
라우트는 기본적으로 대소문자를 무시하므로 /Users가 /users와 일치합니다. app.set('case sensitive routing', true)로 끄거나, 라우터마다 express.Router({ caseSensitive: true })를 쓰세요.
중첩된 라우터에서 req.params가 비어 있는 이유는?
라우터는 자기 경로의 매개변수만 봅니다. /users/:userId/posts에 마운트한다면 express.Router({ mergeParams: true })로 만들어야 부모 경로의 req.params.userId를 쓸 수 있습니다.
모든 라우트를 한 파일에 넣어야 하나요?
라우트가 몇 개를 넘으면 그러지 마세요. 리소스마다 라우터를 하나씩 만들고(routes/users.js, routes/orders.js), module.exports = router로 내보낸 뒤, 메인 파일에서 app.use()로 각각 마운트하세요.