다른 주소로 요청이 들어올 경우 다른 작업을 처리할 수 있도록 라우터를 사용해야 한다.
Koa
자체에 이 기능이 내장되어 있지 않으므로 koa-router
모듈을 설치해야 한다.
$ yarn add koa-router
index.js
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 라우터 설정
router.get('/', (ctx) => {
ctx.body = '홈';
});
router.get('/about', (ctx) => {
ctx.body = '소개';
});
// app 인스턴스에 라우터 적용
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000, () => {
console.log('Listening to port 4000');
});
router.get의 첫 번째 파라미터에는 라우트의 경로가 들어가고, 두 번째 파라미터에는 해당 라우트에 적용할 미들웨어 함수를 넣는다.
라우터의 파라미터를 설정할 때는 /about/:name
형식으로 콜론(:)을 사용하여 라우트 경로를 설정한다.
파라미터가 있을수도 있고 없을 수도 있다면 /about/:name?
같은 형식으로 파라미터 뒤에 물음표를 사용한다.
ctx.params
객체에서 조회할 수 있다.
router.get('/about/:name?', (ctx) => {
const { name } = ctx.params;
ctx.body = name ? `${name}의 소개` : '소개';
});
라우트 파라미터 : /about/react
URL 쿼리의 경우에는 /posts/?id=10
같은 형식으로 요청했다면 해당 값을 ctx.query
에서 조회할 수 있다.
쿼리 문자열을 자동으로 객체 형태로 파싱해 주므로 별도로 파싱 함수를 돌릴 필요가 없다.
router.get('/posts', (ctx) => {
const { id } = ctx.query;
ctx.body = id ? `포스트 #${id}` : '포스트 아이디가 없습니다.';
});
라우트 쿼리 : /posts?id=10
파라미터와 쿼리 둘 다 주소를 통해 특정 값을 받아 올 때 사용하지만, 용도가 조금씩 다르다.
파라미터 : 처리할 작업의 카테고리를 받아 오거나 고유 ID 혹은 이름으로 특정 데이터 조회
쿼리 : 옵션에 관련된 정보를 받아온다. 여러 항목을 리스팅하는 API라면 어떤 조건을 만족하는 항목을 보여줄지 또는 어떤 기준으로 정렬할지를 정해야 할 때 쿼리를 사용한다.
브라우저에서 데이터베이스에 직접 접속하여 데이터를 변경한다면 보안상에 문제가 될 수 있다.
그래서 REST API를 만들어서 사용한다.
REST API는 요청 종류에 따라 다른 HTTP 메서드를 사용한다.
POST /posts : 포스트 작성
GET /posts : 포스트 목록 조회
GET /posts/:id : 특정 포스트 조회
POST /posts/:id/commnets : 특정 포스트에 덧글 등록
라우터를 분리시켜서 작성하고, 불러와서 적용하는 방법을 알아보자.
src/api/index.js
const Router = require('koa-router');
const api = new Router();
api.get('/test', (ctx) => {
ctx.body = '성공';
});
// 라우터 내보내기
module.exports = api;
src/index.js
const Koa = require('koa');
const Router = require('koa-router');
const api = require('./api');
const app = new Koa();
const router = new Router();
router.use('/api', api.routes()); // api 라우트 적용
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000, () => {
console.log('Listening to port 4000');
});
/api/test
경로로 요청하면 성공이라는 문자열이 나타난다.
src/api/posts/index.js
const Router = require('koa-router');
const posts = new Router();
const printInfo = (ctx) => {
ctx.body = {
method: ctx.method,
path: ctx.path,
params: ctx.params,
};
};
posts.get('/', printInfo);
posts.post('/', printInfo);
posts.get('/:id', printInfo);
posts.delete('/:id', printInfo);
posts.put('/:id', printInfo);
posts.patch('/:id', printInfo);
module.exports = posts;
src/api/index.js
const posts = require('./posts');
api.use('/posts', posts.routes());
api/posts
형식으로 요청을 하면 body에 해당 요청에 대한 값을 보여준다.
router.get('/', ctx => {
}
함수를 선언하지 않고 두 번째 인자에 바로 넣어줄 수 있다.
처리 함수의 코드가 길면 라우터 설정을 한 눈에 보기 힘들다.
라우트 처리 함수들을 다른 파일로 따로 분리해서 관리할 수 있다.
koa-bodyparser
미들웨어 적용POST, PUT, PATCH 같은 메서드의 Request Body에 JSON 형식으로 데이터를 넣어주면, 이를 파싱하여 서버에서 사용할 수 있게 한다.
$ yarn add koa-bodyparser
src/index.js
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');
const api = require('./api');
const app = new Koa();
const router = new Router();
router.use('/api', api.routes());
app.use(bodyParser());
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000, () => {
console.log('Listening to port 4000');
});
주의할 점은 router를 적용하는 코드의 윗부분에서 bodyparser를 적용해야 한다.
src/api/posts/posts.ctrl.js
let postId = 1;
const posts = [
{
id: 1,
title: '제목',
body: '내용',
},
];
// 포스트 작성
// POST /api/posts
// { title, body }
exports.write = (ctx) => {
// REST API의 Request Body는 ctx.request.body에서 조회할 수 있다.
const { title, body } = ctx.request.body;
postId += 1;
const post = { id: postId, title, body };
posts.push(post);
ctx.body = post;
};
// 포스트 목록 조회
// GET /api/posts
exports.list = (ctx) => {
ctx.body = posts;
};
// 특정 포스트 조회
// GET /api/posts/:id
exports.read = (ctx) => {
const { id } = ctx.params;
// 주어진 id 값으로 포스트를 찾는다.
// 파라미터로 받아 온 값은 문자열 형식이므로
// 파라미터를 숫자로 변환하거나 비교할 p.id 값을 문자열로 변경해야 한다.
const post = posts.find((p) => p.id.toString() === id);
if (!post) {
ctx.status = 404;
ctx.body = {
message: '포스트가 존재하지 않습니다.',
};
return;
}
ctx.body = post;
};
// 특정 포스트 제거
// DELETE /api/posts/:id
exports.remove = (ctx) => {
const { id } = ctx.params;
// 해당 id를 가진 post가 몇 번째인지 확인.
const index = posts.findIndex((p) => p.id.toString() === id);
if (index === -1) {
ctx.status = 404;
ctx.body = {
message: '포스트가 존재하지 않습니다.',
};
return;
}
posts.slice(index, 1);
ctx.status = 204;
};
// 포스트 수정(교체)
// PUT /api/posts/:id
// { title, body }
exports.replace = (ctx) => {
// PUT 메서드는 전체 포스트 정보를 입력하여 데이터를 통째로 교체할 때 사용한다.
const { id } = ctx.params;
const index = posts.findIndex((p) => p.id.toString() === id);
if (index === -1) {
ctx.status = 404;
ctx.body = {
message: '포스트가 존재하지 않습니다.',
};
return;
}
// 전체 객체를 덮어 씌운다.
// 따라서 id를 제외한 기존 정보를 날리고 객체를 새로 만든다.
posts[index] = {
id,
...ctx.request.body,
};
ctx.body = posts[index];
};
// 포스트 수정(특정 필드 변경)
// PATCH /api/posts/:id
// { title, body }
exports.update = (ctx) => {
// PATCH 메서드는 주어진 필드만 교체
const { id } = ctx.params;
const index = posts.findIndex((p) => p.id.toString() === id);
if (index === -1) {
ctx.status = 404;
ctx.body = {
message: '포스트가 존재하지 않습니다.',
};
return;
}
// 기존 값에 정보를 덮어 씌웁니다.
posts[index] = {
...posts[index],
...ctx.request.body,
};
ctx.body = posts[index];
};
src/api/posts/index.js
const Router = require('koa-router');
const postsCtrl = require('./posts.ctrl');
const posts = new Router();
posts.get('/', postsCtrl.list);
posts.post('/', postsCtrl.write);
posts.get('/:id', postsCtrl.read);
posts.delete('/:id', postsCtrl.remove);
posts.put('/:id', postsCtrl.replace);
posts.patch('/:id', postsCtrl.update);
module.exports = posts;
Postman을 통해서 Request Body를 넣을 수 있다.
update(PATCH)는 기존 값은 유지하면서 새로 값을 덮어 씌운다.
replace(PUT)은 Request Body로 받은 값이 id를 제외한 모든 값을 대체한다.