본문으로 건너뛰기
김신건의 로그

[Framework] Koa.js

· 수정 · 📖 약 3분 · 880자/단어 #koa #nodejs #javascript #framework #backend
Koa, Koa.js, koajs, koa framework, koa nodejs, TJ Holowaychuk Koa, koa 3

정의

Koa.js 는 Express 창시자 TJ Holowaychuk 이 2013년 발표한 Node.js 웹 프레임워크 입니다. Express 의 후계자 성격이며, 미들웨어를 async/await 로 자연스럽게 표현 하고 양파 (onion) 모델 로 요청/응답을 감쌉니다. 초경량 코어 (약 500 LOC), 라우팅과 body parsing 은 별도 패키지.

2024-2025 년 현재 Koa 2.15+ 또는 Koa 3.x (async/await native, Node 20+) 이 사용됩니다.

왜 Koa 인가

  • 작고 명료: 코어에 라우팅 없음. 필요한 것만 조합.
  • async/await 우선: Express 의 callback 지옥 없음.
  • Onion middleware: await next() 로 downstream + upstream 를 자연스럽게.
  • Context (ctx): request + response 를 감싼 하나의 객체.
  • Error 처리 단순: try/catch 로 미들웨어 예외 잡음.
  • 경량: 저지연, 오버헤드 최소.

단점:

  • 생태계가 Express 대비 좁음
  • 큰 앱 구조는 스스로 설계 (NestJS 같은 opinionated 프레임워크 없음)
  • 학습 자료 (블로그, 예시) 적음

설치 & 최소 예제

mkdir myapp && cd myapp
npm init -y
npm i koa
// server.js
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx) => {
  ctx.body = 'Hello Koa';
});

app.listen(3000);
node server.js
# curl http://localhost:3000  ->  Hello Koa

Context (ctx)

Koa 의 핵심. request + response 통합.

app.use(async (ctx, next) => {
  console.log(ctx.method);           // 'GET'
  console.log(ctx.path);              // '/users/1'
  console.log(ctx.query);             // {page: '1'}
  console.log(ctx.headers);           // 헤더
  console.log(ctx.request.body);      // body (parser 필요)

  ctx.status = 201;
  ctx.body = {ok: true};              // 자동 JSON 직렬화
  ctx.set('X-Custom', 'value');       // 응답 헤더
});

자세한 것은 Koa Context 참조.

Middleware (Onion 모델)

app.use(async (ctx, next) => {
  console.log('A pre');
  await next();
  console.log('A post');
});

app.use(async (ctx, next) => {
  console.log('B pre');
  await next();
  console.log('B post');
});

app.use(async (ctx) => {
  console.log('C');
  ctx.body = 'ok';
});

// 요청 시 출력:
// A pre
//   B pre
//     C
//   B post
// A post

각 미들웨어의 await next() 이전은 downstream 방향, 이후는 upstream 방향. 요청 처리 완료를 순차 관측 가능.

자세한 것은 Koa Middleware 참조.

라우팅 (@koa/router)

npm i @koa/router
const Koa = require('koa');
const Router = require('@koa/router');

const app = new Koa();
const router = new Router();

router.get('/', async (ctx) => {
  ctx.body = 'Home';
});

router.get('/users/:id', async (ctx) => {
  ctx.body = { id: ctx.params.id };
});

router.post('/users', async (ctx) => {
  ctx.body = ctx.request.body;
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);

자세한 것은 Koa Router 참조.

Body Parser (@koa/bodyparser)

npm i @koa/bodyparser
const bodyParser = require('@koa/bodyparser');
app.use(bodyParser());

router.post('/echo', async (ctx) => {
  ctx.body = ctx.request.body;
});

자세한 것은 Koa Body Parsing 참조.

흔한 조합 (스택)

목적패키지
라우팅@koa/router
Body@koa/bodyparser, koa-body (파일 업로드)
Multipart@koa/multer
CORS@koa/cors
Sessionkoa-session
Cookie내장 (ctx.cookies)
Compressionkoa-compress
Loggerkoa-logger, koa-pino-logger
Statickoa-static
JWTkoa-jwt, jose
Rate limitkoa-ratelimit
Views (Jinja)@ladjs/koa-views
Error내장 + app.on('error', ...)

Async / Promise 지원

Koa 는 처음부터 async 지원. 미들웨어가 Promise 를 리턴하거나 async 함수.

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

Error 처리

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {
      error: err.message,
      ...(process.env.NODE_ENV !== 'production' && {stack: err.stack}),
    };
    ctx.app.emit('error', err, ctx);
  }
});

app.on('error', (err, ctx) => {
  console.error('server error', err);
});

Koa 는 기본적으로 ctx.status = 4xx/5xx + ctx.throw(4xx, 'message') 지원. 자세한 것은 Koa Error Handling 참조.

응답 반환

ctx.body = 'string';       // text/plain (또는 text/html 자동 감지)
ctx.body = { a: 1 };       // JSON
ctx.body = buffer;         // binary
ctx.body = stream;         // streaming
ctx.body = null;           // 204 no content

Status:

ctx.status = 201;
ctx.throw(404, 'Not found');       // 예외 발생
ctx.assert(user, 401, 'Login required');

Header:

ctx.set('Content-Type', 'application/json');
ctx.append('Set-Cookie', '...');
ctx.remove('X-Foo');

Redirect:

ctx.redirect('/login');
ctx.redirect('back', '/');    // Referer 기반

Config

Koa 자체는 config 없음. dotenv, config, zod 조합.

require('dotenv').config();

const app = new Koa();
app.proxy = process.env.NODE_ENV === 'production';   // X-Forwarded-* 신뢰

TypeScript 지원

@types/koa, @types/koa__router. 하지만 완전한 TS-first 는 아님. NestJS 처럼 완전한 타입 안전 원하면 그쪽.

import Koa from 'koa';
import Router from '@koa/router';

interface AppState { user?: User; }
interface AppContext { db: DatabaseService; }

const app = new Koa<AppState, AppContext>();
const router = new Router<AppState, AppContext>();

router.get('/me', async (ctx) => {
  const user = ctx.state.user;   // typed
  const users = await ctx.db.query(...);  // typed
  ctx.body = user;
});

ctx.state 는 request 스코프 저장, ctx 확장은 앱 스코프.

프로젝트 구조 (실전)

myapp/
├── src/
│   ├── app.js                # createApp
│   ├── server.js              # 진입점 (listen)
│   ├── config/
│   │   └── index.js
│   ├── middleware/
│   │   ├── error.js
│   │   ├── logger.js
│   │   ├── auth.js
│   │   └── request-id.js
│   ├── routes/
│   │   ├── index.js           # router combine
│   │   ├── users.js
│   │   └── posts.js
│   ├── services/
│   │   └── db.js
│   ├── models/
│   └── utils/
├── test/
├── package.json
└── .env

app.js:

const Koa = require('koa');
const bodyParser = require('@koa/bodyparser');
const cors = require('@koa/cors');
const helmet = require('koa-helmet');

const errorMiddleware = require('./middleware/error');
const loggerMiddleware = require('./middleware/logger');
const routes = require('./routes');

function createApp() {
  const app = new Koa();
  app.proxy = true;

  app.use(errorMiddleware);
  app.use(loggerMiddleware);
  app.use(helmet());
  app.use(cors({origin: 'https://app.example.com', credentials: true}));
  app.use(bodyParser({jsonLimit: '1mb'}));
  app.use(routes.routes()).use(routes.allowedMethods());

  return app;
}

module.exports = createApp;

server.js:

const createApp = require('./app');
const app = createApp();

const server = app.listen(process.env.PORT || 3000, () => {
  console.log(`Listening on ${process.env.PORT || 3000}`);
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  server.close(() => process.exit(0));
});

Fastify 와의 관계

Fastify 는 Koa 이후 등장한 성능 지향 프레임워크. Fastify 가 더 빠르고 (JSON stringify 캐싱, plugin 시스템), 스키마 지원 우수. Koa 는 가벼움 + async/await 초점, Fastify 는 성능 + 표준화.

Express 와의 관계

Koa 는 Express 의 후계자로 시작. TJ 가 Express 를 인수인계 후 Koa 를 만듦. 오늘날 두 프레임워크 병존. 자세한 것은 Koa vs Express 참조.

실전 예: JSON API

const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('@koa/bodyparser');

const app = new Koa();
const router = new Router();

// Global error
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {error: err.message};
  }
});

app.use(bodyParser());

// Routes
router.get('/users', async (ctx) => {
  ctx.body = await getUsersFromDb();
});

router.get('/users/:id', async (ctx) => {
  const user = await getUserById(ctx.params.id);
  ctx.assert(user, 404, 'User not found');
  ctx.body = user;
});

router.post('/users', async (ctx) => {
  const {email, name} = ctx.request.body;
  ctx.assert(email, 400, 'email required');
  const user = await createUser({email, name});
  ctx.status = 201;
  ctx.body = user;
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);

함정

WARNING

ctx.body 를 여러 번 할당 하면 마지막 값이 응답. 실수로 downstream 에서 override.

CAUTION

await next() 잊으면 hang. Middleware chain 이 다음 단계로 못 감.

WARNING

Prod 에서 app.proxy = true 안 하면 X-Forwarded-For 무시. ctx.ip 가 프록시 IP 반환.

IMPORTANT

Koa 는 라우팅 X, body parser X, cookie parser X 를 별도 설치. Express 에서 온 사람들이 자주 실수.

CAUTION

Middleware 순서. Error handler 를 첫 middleware 로. Body parser 를 라우터 앞에.

관련 위키

이 글의 용어 (13개)
[Framework] NestJSframeworks
정의 NestJS 는 Kamil Myśliwiec 이 2017년 발표한 Node.js 서버측 프레임워크 입니다. TypeScript first, Angular 에서 영감받은 모듈…
[Javascript] async/awaitjavascript
정의 / 는 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다. 전체 동작 메커니즘은 글 참조…
[Javascript] Promisejavascript
정의 Promise 는 미래의 어떤 시점에 결정될 값을 나타내는 객체다. ECMAScript 2015 (ES6) 에 도입되어 기반 비동기의 가독성과 에러 처리 문제를 해결했다. …
[Koa] Body Parsingframeworks
정의 Koa Body Parsing 은 HTTP 요청 body 를 파싱해 에 사용 가능한 형태로 만드는 미들웨어입니다. Koa 코어는 body parser 를 포함하지 않으므로 …
[Koa] Context (ctx)frameworks
정의 Koa Context ( ) 는 각 요청마다 생성되어 미들웨어와 route handler 에 전달되는 객체입니다. (Node 원본), (Koa 확장), (Koa 확장), (…
[Koa] Deployment (PM2, Docker, Kubernetes)frameworks
정의 Koa Deployment 는 Node.js 프로덕션 배포의 표준 도구 (Docker, Kubernetes, PM2) + Koa 특화 고려사항 (graceful shutdo…
[Koa] Error Handlingframeworks
정의 Koa Error Handling 은 미들웨어 chain 에서 발생한 예외를 catch 하여 응답을 정형화하는 패턴입니다. Koa 는 자동 error handler 를 제공…
[Koa] Koa vs Expressframeworks
정의 Koa 와 Express 는 모두 Node.js 웹 프레임워크이지만 철학과 API 스타일이 다릅니다. Express 는 오래됐고 방대한 생태계 (2010), Koa 는 as…
[Koa] Middleware (Onion Model)frameworks
정의 Koa Middleware 는 요청/응답 사이에 실행되는 async 함수입니다. 각 미들웨어는 (context) 와 (다음 미들웨어를 실행하는 함수) 를 받고, 로 down…
[Koa] Router (@koa/router)frameworks
정의 @koa/router 는 Koa 의 공식 라우터입니다. Koa 코어는 라우팅을 제공하지 않으므로 별도 설치가 필요합니다. Express 스타일 라우팅 (HTTP method…
[Koa] Testing (Supertest, Jest, Vitest)frameworks
정의 Koa Testing 은 (HTTP 호출) + / (assertions/runner) 조합이 표준입니다. 을 Supertest 에 전달하면 실제 HTTP 서버 없이 요청/응…
[Python] FastAPIfastapi
정의 FastAPI 는 Python 3.8+ 을 위한 ASGI 기반 현대 웹/API 프레임워크 입니다. Sebastián Ramírez (tiangolo) 가 2018년 발표했고…
[Python] Flaskflask
정의 Flask 는 Armin Ronacher 가 2010년 발표한 WSGI 기반 Python 마이크로프레임워크 입니다. Pallets Projects 가 유지관리하고, 2026…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기