벨로퍼트 님의 "리액트를 다루는 기술" 21챕터에 대한 내용이다.
Express
의 단점을 보완한 Koa
프레임워크를 통해 node
기반 백엔드를 구축하는 방법을 배운다.
Express
와 다르게 Koa
는 미들웨어의 배열로 구성되어 있어 미들웨어 기능만을 가지고 있다. 따라서 라우팅, 파일 호스팅 등등의 다양한 기능들은 모두 라이브러리를 적용하여야 한다.
Koa
는 우리가 사용하고픈 기능들만 불러와서 서버를 만들 수 있어Express
에 비해 훨씬 가볍다.
또한 async/await
문법을 정식으로 지원하기 때문에 비동기 작업을 편하게 처리할 수 있다!
Koa
의 미들웨어 함수는 다음과 같은 구조로 이루어져 있다.
(ctx, next) => {
}
ctx
는 Context
의 줄임말로 웹 요청과 응답에 관한 정보를 가진다. next
는 현재 처리 중인 미들웨어의 다음 미들웨어를 호출한다.
만일 미들웨어에서 next
를 사용하지 않으면 아래와 같은 형태로 next
를 전달하지 않아도 좋다. 일반적으로 다음 미들웨어가 필요없는 라우트 미들웨어를 설정할 때 이러한 구조로 작성한다.
미들웨어는 app.use
를 사용해 등록되는 순서대로 처리한다.
이때 next
를 호출하지 않으면 해당 미들웨어까지만 실행하고 그 아래 미들웨어는 모두 무시된다.
다음과 같은 설정으로 쿼리 파라미터를 이용한 조작도 가능하다.
app.use((ctx, next) => {
console.log(ctx.url); //context 내부의 url을 호출. 현재 페이지에 대한 url을 불러온다.
console.log(1);
if (ctx.query.authorized !== "1") {
ctx.status = 401; // Unauthorized
return; // url 뒤에 ?authorized=1이 있어야 올바른 body를 호출함.
}
next();
});
또한 next
함수는 Promise
를 반환한다. 이는 Express
와 차별화되는 부분인데,
이 Promise
는 다음에 처리할 미들웨어가 끝나야 완료된다. 이러한 특징을 이용하면 미들웨어 간의 실행 순서를 쉽게 조정할 수 있을 것이다.
위에 언급한 것처럼 Koa
는 async/await
을 정식으로 지원한다. 다음 코드를 살펴보자.
app.use(async (ctx, next) => {
console.log(ctx.url); //context 내부의 url을 호출. 현재 페이지에 대한 url을 불러온다.
console.log(1);
if (ctx.query.authorized !== "1") {
ctx.status = 401; // Unauthorized
return;
}
await next(); // 뒤의 미들웨어를 실행한 뒤 END를 찍는다.
console.log("END"); // 미들웨어를 실행한 뒤 작업을 수행. Koa의 미들웨어는 Promise를 반환하고 이 Promise는 다음에 처리할 미들웨어가 끝나야만 반환된다.
});
Koa
는 내장 라우터 기능이 없으므로 라이브러리를 적용해야한다.
$ yarn add koa-router
다음 코드를 통해 기본적인 적용법을 살펴보자.
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과 router가 연동된다.
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000, () => {
console.log("Listening to port 4000");
});
라우트의 파라미터를 설정하는 것은 일반적인 형식과 비슷하다.
/about/:name
과 같은 형식으로 라우트 경로를 설정한다.
파라미터가 조건부로 존재한다면/about/:name?
과 같은 형식으로 설정한다.
이렇게 설정된 파라미터는 함수의 ctx.params
객체를 통해 조회할 수 있다.
쿼리 문자열을 자동으로 객체 형태로 파싱해 주므로 별도의 파싱 함수를 돌릴 필요가 없다!!!(문자열 형태의 쿼리 문자열 조회에는
ctx.querystring
을 사용한다!)
다음 예시를 살펴보자.
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/:name?", ctx => {
const { name } = ctx.params;
// name의 존재 여부에 따라 다른 결과 출력
ctx.body = name ? `${name}의 소개` : "소개";
});
router.get("/posts", ctx => {
const { id } = ctx.query;
// id의 존재 여부에 따라 다른 결과 출력
ctx.body = id ? `포스트 #${id}` : "포스트 아이디가 없습니다.";
});
// app 인스턴스에 라우터 적용 -> 그래야 app과 router가 연동된다.
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000, () => {
console.log("Listening to port 4000");
});
위에서 name
은 파라미터로 불러왔고, id는 쿼리했다. 이처럼 파라미터와 쿼리는 둘 다 주소를 통해 특정 값을 받아올 때 사용하지만, 용도가 조금 다르다.
파라미터는 처리할 작업의 카테고리를 받아오거나, 고유 ID 혹은 이름으로 특정 데이터를 조회할 때 사용한다.
쿼리는 옵션에 관련된 정보를 받아온다. 예를 들어 여러 항목을 리스팅하는 API라면, 어떤 조건을 만족하는 항목을 보여줄지 또는 어떤 기준으로 정렬할지를 정해야 할 때 쿼리를 사용한다.
애플리케이션을 제작할 때 라우트 경로가 아주 많아지고 복잡해질 가능성이 크다.
이러한 경우 특정 용도나 목적에 따라 라우트를 분류하고 코드를 작게 쪼개는 작업은 필수적인데, Koa
를 이용한 라우트 모듈화가 어떻게 이루어지는지 알아보자.
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 인스턴스에 라우터 적용 -> 그래야 app과 router가 연동된다.
app.use(router.routes()).use(router.allowedMethods());
app.listen(4000, () => {
console.log("Listening to port 4000");
});
위의 코드 예시처럼 src
디렉토리에 api
디렉토리를 추가로 만들고, 그곳에서 Router
를 이용해 새로운 api
를 제작한다. 이후 module.exports
를 이용해 export
하면 이후 src
디렉토리에서 라우터를 적용할 수 있게 된다.
지금까지 Koa
프레임워크에 대한 특징과 짧은 예시들을 살펴보았다.
앞으로 계속 Koa
에 익숙해지는 시간을 가지면서 백엔드에 적응해보자.