Introduction
Koa.js is a minimalist Node.js framework created by the team behind Express. It emphasizes generators and async/await for cleaner, more readable code. Unlike Express, Koa does not include a built-in router or default error handling, giving you complete freedom. This intermediate tutorial guides you through building a complete REST API: from a basic server to advanced error management and middleware. You'll learn how to structure a professional project while avoiding common pitfalls in asynchronous applications.
Prerequisites
- Node.js 20+
- Basic knowledge of modern JavaScript and async/await
- npm or yarn installed
- A code editor (VS Code recommended)
Project Initialization
mkdir koa-api-2026 && cd koa-api-2026
npm init -y
npm install koa koa-router @koa/cors dotenv
npm install --save-dev nodemonWe initialize the project and install Koa with its official router, the CORS middleware, and dotenv for environment variables. Nodemon simplifies development by automatically restarting the server.
Creating the Base Server
import Koa from 'koa';
import dotenv from 'dotenv';
dotenv.config();
const app = new Koa();
const PORT = process.env.PORT || 3000;
app.use(async (ctx) => {
ctx.body = { message: 'API Koa opérationnelle' };
});
app.listen(PORT, () => {
console.log(`Serveur démarré sur le port ${PORT}`);
});This file creates a minimal Koa instance. The default middleware responds to all requests. We will use separate files for routing in the following steps.
Setting Up Routing
import Router from 'koa-router';
const router = new Router({ prefix: '/api/users' });
router.get('/', async (ctx) => {
ctx.body = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
});
router.get('/:id', async (ctx) => {
ctx.body = { id: ctx.params.id, name: 'Utilisateur ' + ctx.params.id };
});
export default router;We create a router with a prefix and two GET routes. ctx.params allows retrieving URL parameters in a simple and readable way.
Integrating Middlewares
import Koa from 'koa';
import Router from 'koa-router';
import cors from '@koa/cors';
import usersRouter from './routes/users.js';
const app = new Koa();
const router = new Router();
app.use(cors());
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
router.use(usersRouter.routes());
app.use(router.routes());
app.use(router.allowedMethods());
export default app;Middlewares are executed in the order they are added. The timing middleware measures the duration of each request. We then connect the user routes.
Centralized Error Handling
export default async function errorHandler(ctx, next) {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
success: false,
message: err.message || 'Erreur interne du serveur'
};
ctx.app.emit('error', err, ctx);
}
}This middleware intercepts all errors, returns a consistent JSON response, and emits the error event for centralized logging.
Best Practices
- Always place the error handling middleware first
- Use route prefixes to organize the API
- Validate input data with a schema (e.g., Joi or Zod)
- Separate routes, middlewares, and controllers into distinct files
- Log errors with a structured tool like Winston or Pino
Common Mistakes to Avoid
- Forgetting to call await next() in a middleware (blocks the chain)
- Not handling asynchronous errors (uncaught rejected promises)
- Using ctx.throw without an appropriate HTTP status
- Installing too many global middlewares that slow down all requests
Going Further
Discover our advanced training on modern Node.js frameworks and API architectures: https://learni-group.com/formations