Koa.js Complete Guide | Next Generation Node.js Framework
이 글의 핵심
Koa.js is a next-generation web framework for Node.js designed by the Express team. It uses async/await for cleaner async code and provides a smaller, more expressive foundation.
Introduction
Koa.js is a new web framework designed by the team behind Express. It aims to be a smaller, more expressive, and more robust foundation for web applications and APIs through async functions.
Express vs Koa
Express (callback-based):
app.get('/users', (req, res, next) => {
User.find((err, users) => {
if (err) return next(err);
res.json(users);
});
});
Koa (async/await):
router.get('/users', async (ctx) => {
ctx.body = await User.find();
});
Key Differences:
- Koa uses
async/await(no callbacks) - Single
ctxobject instead ofreq/res - No built-in routing or middleware (smaller core)
- Better error handling with try/catch
Real-World Adoption
Koa represents the evolution of Node.js web frameworks:
Enterprise Usage:
- Alibaba: Uses Koa in Egg.js framework for enterprise Node.js applications
- Tencent: Powers internal services with Koa for its modern async architecture
- Ant Design Pro: Built on UmiJS which uses Koa for development server
- ThinkJS: Popular Chinese Node.js framework built on top of Koa
Market Position:
- 1.2+ million weekly npm downloads (April 2026) - solid adoption among modern Node.js developers
- 35,000+ GitHub stars - strong community following
- Created by TJ Holowaychuk - the original creator of Express, bringing years of web framework expertise
- Used in 100,000+ public GitHub repositories
Why Teams Choose Koa:
- Modern async/await: Native async patterns instead of callback hell
- Lightweight core: Only 550 lines of code (Express: 2,500+ lines)
- Better error handling: async/await enables try/catch for proper error boundaries
- Clean middleware system: Onion-model middleware with upstream/downstream flow
- No built-in features: Choose your own routing, body parsing, validation (flexibility)
Framework Evolution:
- Egg.js: Enterprise-grade Koa framework by Alibaba (10K+ stars)
- ThinkJS: Full-stack Koa framework with built-in ORM
- AdonisJS 5+: Uses Koa’s context model internally
When to Choose Koa:
- ✅ Starting new projects with modern Node.js (v12+)
- ✅ Team comfortable with async/await
- ✅ Need minimal framework with maximum flexibility
- ❌ Need mature ecosystem with thousands of middleware (use Express)
- ❌ Beginner-friendly framework with conventions (use NestJS)
Koa is perfect for teams that want Express’s simplicity but with modern JavaScript patterns and cleaner async code.
1. Installation
npm install koa @koa/router
Basic Server:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
ctx.body = 'Hello World';
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
2. Context Object
Koa uses a single ctx (context) object:
app.use(async (ctx) => {
// Request
console.log(ctx.method); // GET
console.log(ctx.url); // /users
console.log(ctx.path); // /users
console.log(ctx.query); // { limit: '10' }
console.log(ctx.headers); // { ... }
console.log(ctx.request.body); // Requires koa-bodyparser
// Response
ctx.status = 200;
ctx.body = { message: 'Hello' };
ctx.set('X-Custom', 'value');
// Helpers
ctx.throw(400, 'Bad Request');
ctx.redirect('/new-url');
ctx.assert(ctx.state.user, 401, 'Unauthorized');
});
3. Middleware
Basic Middleware
const Koa = require('koa');
const app = new Koa();
// Logger middleware
app.use(async (ctx, next) => {
const start = Date.now();
await next(); // Call next middleware
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// Response middleware
app.use(async (ctx) => {
ctx.body = 'Hello World';
});
app.listen(3000);
Error Handling Middleware
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);
}
});
// Error event listener
app.on('error', (err, ctx) => {
console.error('Server error:', err);
});
Common Middleware
npm install koa-bodyparser koa-logger koa-cors koa-helmet
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const logger = require('koa-logger');
const cors = require('@koa/cors');
const helmet = require('koa-helmet');
const app = new Koa();
app.use(helmet());
app.use(cors());
app.use(logger());
app.use(bodyParser());
4. Routing
npm install @koa/router
const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();
// Basic routes
router.get('/', async (ctx) => {
ctx.body = 'Home';
});
router.get('/users', async (ctx) => {
ctx.body = [{ id: 1, name: 'John' }];
});
router.post('/users', async (ctx) => {
const user = ctx.request.body;
ctx.status = 201;
ctx.body = user;
});
// Route parameters
router.get('/users/:id', async (ctx) => {
ctx.body = { id: ctx.params.id };
});
// Query strings
router.get('/search', async (ctx) => {
const { q, limit = 20 } = ctx.query;
ctx.body = { query: q, limit };
});
// Use router
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
Nested Routers
// routes/users.js
const Router = require('@koa/router');
const router = new Router({ prefix: '/users' });
router.get('/', async (ctx) => {
ctx.body = 'Get all users';
});
router.get('/:id', async (ctx) => {
ctx.body = `Get user ${ctx.params.id}`;
});
module.exports = router;
// app.js
const usersRouter = require('./routes/users');
app.use(usersRouter.routes());
5. REST API Example
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router({ prefix: '/api' });
app.use(bodyParser());
let todos = [
{ id: 1, text: 'Learn Koa', done: false },
{ id: 2, text: 'Build API', done: false },
];
// Get all todos
router.get('/todos', async (ctx) => {
ctx.body = todos;
});
// Get single todo
router.get('/todos/:id', async (ctx) => {
const todo = todos.find(t => t.id === parseInt(ctx.params.id));
if (!todo) {
ctx.throw(404, 'Todo not found');
}
ctx.body = todo;
});
// Create todo
router.post('/todos', async (ctx) => {
const { text } = ctx.request.body;
ctx.assert(text, 400, 'Text is required');
const todo = {
id: todos.length + 1,
text,
done: false,
};
todos.push(todo);
ctx.status = 201;
ctx.body = todo;
});
// Update todo
router.put('/todos/:id', async (ctx) => {
const todo = todos.find(t => t.id === parseInt(ctx.params.id));
if (!todo) {
ctx.throw(404, 'Todo not found');
}
Object.assign(todo, ctx.request.body);
ctx.body = todo;
});
// Delete todo
router.delete('/todos/:id', async (ctx) => {
const index = todos.findIndex(t => t.id === parseInt(ctx.params.id));
if (index === -1) {
ctx.throw(404, 'Todo not found');
}
todos.splice(index, 1);
ctx.status = 204;
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
6. Authentication
npm install jsonwebtoken bcryptjs
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const JWT_SECRET = 'your-secret-key';
// Register
router.post('/auth/register', async (ctx) => {
const { email, password } = ctx.request.body;
const hashedPassword = await bcrypt.hash(password, 10);
// Save user to database...
const user = { id: 1, email, password: hashedPassword };
ctx.status = 201;
ctx.body = { message: 'User created' };
});
// Login
router.post('/auth/login', async (ctx) => {
const { email, password } = ctx.request.body;
// Find user in database...
const user = { id: 1, email, password: '$2a$10$...' };
const isValid = await bcrypt.compare(password, user.password);
ctx.assert(isValid, 401, 'Invalid credentials');
const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });
ctx.body = { token };
});
// Auth middleware
const authenticate = async (ctx, next) => {
const token = ctx.headers.authorization?.replace('Bearer ', '');
ctx.assert(token, 401, 'Unauthorized');
try {
const decoded = jwt.verify(token, JWT_SECRET);
ctx.state.userId = decoded.userId;
await next();
} catch (error) {
ctx.throw(401, 'Invalid token');
}
};
// Protected route
router.get('/profile', authenticate, async (ctx) => {
ctx.body = { userId: ctx.state.userId };
});
7. File Upload
npm install @koa/multer multer
const multer = require('@koa/multer');
const upload = multer({ dest: 'uploads/' });
// Single file
router.post('/upload', upload.single('file'), async (ctx) => {
ctx.body = { file: ctx.file };
});
// Multiple files
router.post('/upload-multiple', upload.array('files', 5), async (ctx) => {
ctx.body = { files: ctx.files };
});
8. Database Integration
With Mongoose
npm install mongoose
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/myapp');
const User = mongoose.model('User', {
name: String,
email: String,
});
router.get('/users', async (ctx) => {
ctx.body = await User.find();
});
router.post('/users', async (ctx) => {
const user = new User(ctx.request.body);
await user.save();
ctx.status = 201;
ctx.body = user;
});
With Prisma
npm install @prisma/client
npx prisma init
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
router.get('/users', async (ctx) => {
ctx.body = await prisma.user.findMany();
});
router.post('/users', async (ctx) => {
ctx.body = await prisma.user.create({
data: ctx.request.body
});
});
9. Validation
npm install koa-validate
const validate = require('koa-validate');
validate(app);
router.post('/users', async (ctx) => {
ctx.checkBody('email').notEmpty().isEmail();
ctx.checkBody('password').notEmpty().len(6, 20);
if (ctx.errors) {
ctx.status = 400;
ctx.body = { errors: ctx.errors };
return;
}
// Create user...
});
10. Best Practices
1. Error Handling
// Global error handler
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
status: 'error',
message: err.message
};
// Log error
if (ctx.status === 500) {
console.error(err);
}
}
});
// Custom error class
class AppError extends Error {
constructor(message, status = 500) {
super(message);
this.status = status;
}
}
// Usage
router.get('/users/:id', async (ctx) => {
const user = await User.findById(ctx.params.id);
if (!user) {
throw new AppError('User not found', 404);
}
ctx.body = user;
});
2. Environment Variables
require('dotenv').config();
const PORT = process.env.PORT || 3000;
const DATABASE_URL = process.env.DATABASE_URL;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
3. Graceful Shutdown
const server = app.listen(3000);
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
server.close(() => {
console.log('HTTP server closed');
// Close database connections
process.exit(0);
});
});
11. Testing
npm install --save-dev jest supertest
const request = require('supertest');
const app = require('./app');
describe('GET /api/users', () => {
it('should return all users', async () => {
const response = await request(app.callback())
.get('/api/users')
.expect(200);
expect(Array.isArray(response.body)).toBe(true);
});
});
Summary
Koa.js is the modern alternative to Express:
- Async/await for clean async code
- Context object instead of req/res
- Smaller core with opt-in features
- Better error handling with try/catch
- Elegant middleware composition
Key Takeaways:
- Use
ctxfor request and response - Async/await for all middleware
ctx.throw()for errors- @koa/router for routing
- Smaller core means more control
Next Steps:
Resources:
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Complete Koa.js guide for modern Node.js applications.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- Express.js Complete Guide | Fast Node.js Web Framework
- Go Web Development Guide | REST APIs· Middleware
- JavaScript Async Debugging Case Study
Keywords Covered in This Article (Related Search Terms)
This article covers Koa.js, Node.js, JavaScript, Backend, REST API, Async.