[Koa] Context (ctx)
정의
Koa Context (ctx) 는 각 요청마다 생성되어 미들웨어와 route handler 에 전달되는 객체입니다. req (Node 원본), request (Koa 확장), response (Koa 확장), state (요청 스코프 저장), 헬퍼 메서드를 한 곳에 통합합니다.
ctx 의 계층
ctx
├── req ← Node 원본 IncomingMessage
├── res ← Node 원본 ServerResponse
├── request ← Koa 확장 Request
│ ├── header, headers
│ ├── method
│ ├── url, originalUrl
│ ├── path
│ ├── query
│ ├── body (bodyparser 뒤)
│ ├── ip, ips
│ └── ...
├── response ← Koa 확장 Response
│ ├── header, headers (set/get)
│ ├── status
│ ├── message
│ ├── body
│ ├── length
│ ├── type
│ └── ...
├── app ← Application
├── state ← 요청 스코프 저장소
├── cookies ← 쿠키 조작
└── (delegated: ctx.body === ctx.response.body 등)
Delegation
편의를 위해 ctx 자체에도 자주 쓰는 속성이 위임되어 있습니다.
ctx.method === ctx.request.method
ctx.path === ctx.request.path
ctx.query === ctx.request.query
ctx.header === ctx.request.header
ctx.body === ctx.response.body // getter/setter
ctx.status === ctx.response.status
ctx.set(...) === ctx.response.set(...)
즉 ctx.body = 'ok' 는 ctx.response.body = 'ok'.
Request 정보
app.use(async (ctx) => {
ctx.method; // 'GET', 'POST', ...
ctx.url; // '/users?page=1' (전체)
ctx.originalUrl; // proxy 이전 URL
ctx.path; // '/users' (query 제외)
ctx.querystring; // 'page=1'
ctx.query; // {page: '1'} (parsed)
ctx.host; // 'example.com:3000'
ctx.hostname; // 'example.com'
ctx.protocol; // 'https'
ctx.secure; // boolean
ctx.ip; // client IP
ctx.ips; // proxy chain (proxy=true 필요)
ctx.header; // request headers
ctx.get('user-agent');
ctx.get('content-type');
ctx.type; // request Content-Type (짧게)
ctx.length; // Content-Length
});
Body (bodyparser 뒤)
const bodyParser = require('@koa/bodyparser');
app.use(bodyParser());
app.use(async (ctx) => {
ctx.request.body; // parsed body
// JSON: {name: 'kim'}
// Form: {name: 'kim'}
// Text: 'raw text'
});
주의: Koa 는 기본 body 를 파싱 안 함. @koa/bodyparser, koa-body, koa-bodyparser 하나를 설치.
자세한 것은 Koa Body Parsing 참조.
Files
koa-body 또는 @koa/multer 로 파일 업로드:
const koaBody = require('koa-body').default;
app.use(koaBody({multipart: true}));
app.use(async (ctx) => {
ctx.request.files; // {upload: File}
});
Response 조작
body
ctx.body = 'string'; // text/html (또는 text/plain 감지)
ctx.body = { key: 'value' }; // JSON
ctx.body = Buffer.from('...'); // binary
ctx.body = fs.createReadStream(path); // stream
ctx.body = null; // 204 no content
status
ctx.status = 200;
ctx.status = 201;
ctx.status = 404;
ctx.body 를 설정하면 status 가 자동으로 200 (또는 204 for null). 명시 없으면 대개 404.
message
ctx.message = 'Custom OK'; // 200 Custom OK
type (Content-Type)
ctx.type = 'json'; // -> application/json
ctx.type = 'html'; // -> text/html
ctx.type = 'text'; // -> text/plain
ctx.type = 'application/xml';
length
ctx.length = 1024; // Content-Length
Auto-computed for string/Buffer, manual for streams.
header 조작
ctx.set('X-Custom', 'value');
ctx.set({'X-A': 'a', 'X-B': 'b'});
ctx.append('Set-Cookie', 'session=abc');
ctx.remove('X-Foo');
ctx.vary('Accept-Encoding');
redirect
ctx.redirect('/login');
ctx.redirect('/login', 301); // status 명시
ctx.redirect('back', '/'); // Referer 기반, alt fallback
기본 302.
attachment (파일 다운로드)
ctx.attachment('report.pdf');
ctx.type = 'application/pdf';
ctx.body = fs.createReadStream('/path/to/report.pdf');
// Content-Disposition: attachment; filename="report.pdf"
ctx.state (Request-scoped 저장소)
미들웨어 간 데이터 전달. Session, 인증된 사용자 등.
app.use(async (ctx, next) => {
ctx.state.user = await getUserFromToken(ctx.get('authorization'));
await next();
});
router.get('/me', async (ctx) => {
ctx.body = ctx.state.user;
});
TypeScript:
interface AppState { user?: User; requestId: string; }
const app = new Koa<AppState>();
ctx.throw() / ctx.assert()
ctx.throw(400, 'Missing email');
ctx.throw(401, 'Unauthorized');
ctx.throw(500, 'Internal', {internal: true}); // 3rd = properties
// assert 는 truthy 검사
ctx.assert(user, 401, 'Login required');
ctx.assert(id, 400, 'ID required');
ctx.throw 는 createError() (http-errors) 사용. try/catch 없이 던지면 default error handler 로.
Cookies
ctx.cookies.get('session');
ctx.cookies.set('session', 'abc', {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 3600_000,
path: '/',
domain: 'example.com',
});
ctx.cookies.set('session', null); // 삭제
Signed cookies (app.keys 필요):
app.keys = ['secret1', 'secret2']; // 첫 번째로 서명, 나머지는 검증
ctx.cookies.set('session', 'abc', {signed: true});
ctx.cookies.get('session', {signed: true}); // 검증 실패 시 undefined
Content Negotiation
ctx.accepts()
ctx.accepts('json'); // 'json' | false
ctx.accepts('html', 'json'); // 순위대로
ctx.accepts('image/png', 'image/tiff');
Accept 헤더 기반.
ctx.acceptsLanguages()
ctx.acceptsLanguages('en', 'ko');
ctx.acceptsEncodings(), ctx.acceptsCharsets()
같은 패턴.
ctx.request vs ctx.req
ctx.req: Node 원본IncomingMessage. 스트림 접근 등.ctx.request: Koa 확장.body,query등 파싱된 것.
미들웨어에서 stream 을 원본으로 처리 (large file upload without body parser):
app.use(async (ctx) => {
ctx.req.pipe(fs.createWriteStream('/tmp/upload'));
});
ctx.response vs ctx.res
ctx.res: Node 원본ServerResponsectx.response: Koa 확장
주의: ctx.res.end() 직접 호출하면 Koa 의 응답 처리 우회. 대신 ctx.body 사용.
Async in ctx
ctx 는 요청마다 새로 생성. AsyncLocalStorage 를 대체하는 자연스러운 request scope.
app.use(async (ctx, next) => {
ctx.state.trace = tracer.startSpan('request');
try {
await next();
} finally {
ctx.state.trace.finish();
}
});
Prototype extension
ctx.app.context 로 모든 요청에 자동 노출되는 헬퍼 추가:
app.context.db = new Database();
// 어느 미들웨어든
async (ctx) => {
const user = await ctx.db.query('...');
};
TypeScript 는 AppContext 인터페이스 확장:
declare module 'koa' {
interface DefaultContext {
db: Database;
}
}
함정
WARNING
ctx.res.end() 호출 금지 (Koa 응답 우회). ctx.body 로.
CAUTION
ctx.request.body 는 body parser 뒤에서만. Parser 전에는 undefined.
WARNING
app.proxy = true 안 하면 ctx.ip, ctx.protocol 이 프록시 뒤에서 오답. Nginx / ALB 뒤 배포 시 필수.
IMPORTANT
ctx.state 는 request scope. ctx.app.context 확장은 app scope (모든 요청 공유).
CAUTION
ctx.throw(500) + downstream 에서 catch 없음 = default error handler 가 500 반환. 사용자에 stack trace 노출 위험.
관련 위키
- Koa.js - 상위 개요
- Koa Middleware - ctx 사용
- Koa Router - ctx.params
- Koa Body Parsing - ctx.request.body
- Koa Error Handling - ctx.throw
- JavaScript async/await
- NestJS Controllers -
@Req대비
이 글의 용어 (7개)
- [Framework] Koa.jsframeworks
- 정의 Koa.js 는 Express 창시자 TJ Holowaychuk 이 2013년 발표한 Node.js 웹 프레임워크 입니다. Express 의 후계자 성격이며, 미들웨어를 a…
- [Javascript] async/awaitjavascript
- 정의 / 는 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다. 전체 동작 메커니즘은 글 참조…
- [Koa] Body Parsingframeworks
- 정의 Koa Body Parsing 은 HTTP 요청 body 를 파싱해 에 사용 가능한 형태로 만드는 미들웨어입니다. Koa 코어는 body parser 를 포함하지 않으므로 …
- [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…
- [NestJS] Controllersframeworks
- 정의 NestJS Controller 는 데코레이터가 붙은 클래스로, HTTP 요청을 라우팅하고 응답을 반환합니다. Method 별로 , , , , 데코레이터를 붙여 URL + …
💬 댓글