[Koa] Koa vs Express
정의
Koa 와 Express 는 모두 Node.js 웹 프레임워크이지만 철학과 API 스타일이 다릅니다. Express 는 오래됐고 방대한 생태계 (2010~), Koa 는 async/await 우선 (2013~). 창시자는 같은 TJ Holowaychuk 이며 Koa 는 Express 의 후계 성격.
한눈에 비교
| 축 | Express | Koa |
|---|---|---|
| 첫 릴리스 | 2010 | 2013 |
| 크기 | 500KB+ (deps) | 매우 작음 (~200KB) |
| 비동기 | callback 기본, async/await 도 가능 | async/await first |
| 미들웨어 모델 | linear (next()) | onion (await next()) |
| Request/Response | req, res 각각 | ctx (통합) |
| 에러 처리 | try/catch 어려움 (callback) | async try/catch 자연 |
| 라우팅 | 내장 (app.get) | 별도 (@koa/router) |
| Body parser | 내장 (express.json()) | 별도 (@koa/bodyparser) |
| 템플릿 | 내장 지원 | 별도 |
| 정적 파일 | 내장 (express.static) | 별도 (koa-static) |
| 커뮤니티 | 매우 큼 | 중간 |
| 생태계 | 방대 (10,000+ 미들웨어) | 작음 (koa- 프리픽스) |
| 채택 | 압도적 다수 | 소수 |
미들웨어 스타일
Express
// callback 스타일
app.get('/users/:id', (req, res, next) => {
User.findById(req.params.id, (err, user) => {
if (err) return next(err);
res.json(user);
});
});
// async 스타일 (수동 try/catch)
app.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
res.json(user);
} catch (err) {
next(err);
}
});
Express 는 async error 를 자동으로 안 잡음. express-async-errors 패키지 또는 wrapper 필요.
Koa
router.get('/users/:id', async (ctx) => {
const user = await User.findById(ctx.params.id);
ctx.body = user;
});
Async error 는 upstream 으로 자동 전파. 미들웨어에서 try/catch.
Middleware chain
Express (linear)
app.use((req, res, next) => {
console.log('1');
next();
console.log('1 after'); // 이는 실행되지만 응답 이후 관측 어려움
});
app.use((req, res, next) => {
console.log('2');
res.send('done');
});
// 출력: 1, 2, 1 after (하지만 response 완료 이후)
Koa (onion)
app.use(async (ctx, next) => {
console.log('1 in');
await next();
console.log('1 out'); // response 이후 확실히 실행
});
app.use(async (ctx) => {
console.log('2');
ctx.body = 'done';
});
// 출력: 1 in, 2, 1 out
Koa 의 onion 모델은 응답 시간 측정, 에러 catch, wrapping 이 자연스럽습니다.
Response 처리
Express
app.get('/', (req, res) => {
res.status(201);
res.set('X-Custom', 'value');
res.json({ok: true});
// 또는 chain
res.status(201).set('X-Custom', 'value').json({ok: true});
});
res.send, res.json, res.render, res.redirect 등 method 다양.
Koa
router.get('/', async (ctx) => {
ctx.status = 201;
ctx.set('X-Custom', 'value');
ctx.body = {ok: true}; // JSON 자동
});
ctx.body 하나로. Type 자동 감지 (string -> text/html, object -> json).
Error 처리
Express
app.use((err, req, res, next) => {
console.error(err);
res.status(err.status || 500).json({error: err.message});
});
// async error 자동 X (v4)
app.get('/', async (req, res, next) => {
try {
await doAsyncWork();
} catch (err) {
next(err); // 수동
}
});
Express 5 에서 async error 를 자동 처리 예정.
Koa
// 첫 미들웨어
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {error: err.message};
ctx.app.emit('error', err, ctx);
}
});
// 그냥 throw
router.get('/', async (ctx) => {
await doAsyncWork(); // 실패 시 자동 upstream
ctx.body = 'ok';
});
성능
- Express: baseline. 특별한 최적화 없음.
- Koa: 조금 빠름 (코어 작음). 미들웨어 오버헤드 감소.
- Fastify: 훨씬 빠름 (JSON 최적화, 스키마 컴파일). 성능이 최우선이면 Fastify.
언제 어느 것을?
Express 유지 / 선택
- 기존 코드베이스
- 광범위한 미들웨어 필요 (Passport strategies, 특수 통합)
- 팀이 Express 익숙
- 튜토리얼/문서/자료 방대 필요
- Express 5+ async 개선 후
Koa 선택
- 새 프로젝트 + async/await 자연스러움 원함
- 가벼운 코어 + 필요한 것만 조합
- Onion 모델 (트레이싱, error handling 통일) 이점
- Express 미들웨어 안 필수
대안 고려
- Fastify: 성능 최상, schema-first
- NestJS: 대규모 앱 구조, TypeScript, DI
- Hono: Cloudflare Workers / Edge 최적
- Elysia: Bun native
코드 예시 나란히
Hello World
// Express
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello'));
app.listen(3000);
// Koa
const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();
router.get('/', ctx => { ctx.body = 'Hello'; });
app.use(router.routes());
app.listen(3000);
JSON API
// Express
app.use(express.json());
app.post('/users', async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
});
// Koa
const bodyParser = require('@koa/bodyparser');
app.use(bodyParser());
router.post('/users', async ctx => {
const user = await User.create(ctx.request.body);
ctx.status = 201;
ctx.body = user;
});
Error handler
// Express
app.use((err, req, res, next) => {
res.status(err.status || 500).json({error: err.message});
});
// Koa (최상단 미들웨어)
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {error: err.message};
}
});
File upload
// Express + multer
const multer = require('multer');
const upload = multer({dest: '/uploads/'});
app.post('/upload', upload.single('file'), (req, res) => {
res.json({filename: req.file.filename});
});
// Koa + @koa/multer
const multer = require('@koa/multer');
const upload = multer({dest: '/uploads/'});
router.post('/upload', upload.single('file'), async ctx => {
ctx.body = {filename: ctx.file.filename};
});
마이그레이션 (Express -> Koa)
주요 변환:
req.method->ctx.method,req.path->ctx.pathreq.body->ctx.request.bodyres.status(200).json(x)->ctx.status = 200; ctx.body = x(req, res, next)->(ctx, next)app.get(...)-> Router 로 이관- Middleware 등록 순서 재검토 (onion model 로)
- 미들웨어 (
express-session,helmet, …) -> koa 버전
대규모 앱은 마이그레이션 노력 큼. 새 프로젝트만 Koa 선택 후 기존은 유지가 관용.
함정
WARNING
Express middleware 는 Koa 에서 못 씀. Express 는 (req, res, next), Koa 는 (ctx, next). koa-connect 로 래핑 가능하지만 완전 X.
CAUTION
Express 5 는 async 자동 처리. Express 4 는 아님. 프로젝트 버전 확인.
WARNING
Koa 는 라우팅/body parser 없음. Express 에서 온 사람이 자주 실수.
IMPORTANT
미들웨어 표기법 다름. Express app.use(m), Koa app.use(m) 은 같아 보이지만 signature 다름.
CAUTION
성능은 프레임워크보다 코드가 지배적. 프레임워크 선택은 팀/생태계가 우선.
관련 위키
- Koa.js - Koa 개요
- Koa Middleware - Onion model
- Koa Context
- Koa Router
- Koa Body Parsing
- Koa Error Handling
- Koa Testing
- Koa Deployment
- NestJS - Opinionated framework
- JavaScript async/await
- JavaScript Callback
- JavaScript Promise
이 글의 용어 (12개)
- [Framework] Koa.jsframeworks
- 정의 Koa.js 는 Express 창시자 TJ Holowaychuk 이 2013년 발표한 Node.js 웹 프레임워크 입니다. Express 의 후계자 성격이며, 미들웨어를 a…
- [Framework] NestJSframeworks
- 정의 NestJS 는 Kamil Myśliwiec 이 2017년 발표한 Node.js 서버측 프레임워크 입니다. TypeScript first, Angular 에서 영감받은 모듈…
- [Javascript] 콜백javascript
- 정의 콜백 (callback): 다른 함수의 인자로 넘겨져, 받은 쪽(caller)이 적절한 시점에 호출하는 함수. 특성이 있어야 가능한 패턴이다. JavaScript 에서는 함…
- [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] 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 서버 없이 요청/응…
💬 댓글