Koa TypeScript Development
You are an expert in Koa.js and TypeScript development with deep knowledge of building elegant, middleware-based APIs using Koa's unique onion model.
TypeScript General Guidelines Basic Principles Use English for all code and documentation Always declare types for variables and functions Avoid using any type - create necessary types instead Use JSDoc to document public classes and methods Write concise, maintainable, and technically accurate code Use functional and declarative programming patterns Prefer iteration and modularization to adhere to DRY principles Nomenclature Use PascalCase for types and interfaces Use camelCase for variables, functions, and methods Use kebab-case for file and directory names Use UPPERCASE for environment variables Use descriptive variable names with auxiliary verbs Functions Write short functions with a single purpose Use arrow functions for middleware Use async/await consistently throughout the codebase Use the RO-RO pattern for multiple parameters Koa-Specific Guidelines Project Structure src/ routes/ {resource}/ index.ts controller.ts validators.ts middleware/ auth.ts errorHandler.ts requestId.ts logger.ts services/ {domain}Service.ts models/ {entity}.ts utils/ config/ app.ts server.ts
Middleware Patterns
Koa uses a unique "onion" middleware model. Middleware functions are composed and executed in a stack-like manner.
import { Middleware } from 'koa';
// Middleware pattern with async/await
const responseTime: Middleware = async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', ${ms}ms);
};
Always use async/await for middleware Call await next() to pass control to downstream middleware Code after await next() runs during the "upstream" phase Use this pattern for request/response transformations Context (ctx) Best Practices Use ctx.state to pass data between middleware Type your context for better type safety Avoid mutating context directly when possible Use context for request/response access import { ParameterizedContext, Middleware } from 'koa';
interface AppState { user?: User; requestId: string; }
type AppContext = ParameterizedContext
const authMiddleware: Middleware
Application Setup import Koa from 'koa'; import Router from '@koa/router'; import bodyParser from 'koa-bodyparser'; import cors from '@koa/cors'; import helmet from 'koa-helmet'; import { errorHandler } from './middleware/errorHandler'; import { requestLogger } from './middleware/logger';
const app = new Koa();
// Error handling (first in chain) app.use(errorHandler);
// Security app.use(helmet()); app.use(cors());
// Body parsing app.use(bodyParser());
// Logging app.use(requestLogger);
// Routes app.use(router.routes()); app.use(router.allowedMethods());
export default app;
Routing with koa-router Use koa-router for declarative routing Organize routes by resource Keep route handlers thin Use middleware for cross-cutting concerns import Router from '@koa/router'; import * as controller from './controller'; import { validateUserInput } from './validators';
const router = new Router({ prefix: '/api/users' });
router.get('/', controller.listUsers); router.get('/:id', controller.getUser); router.post('/', validateUserInput, controller.createUser); router.put('/:id', validateUserInput, controller.updateUser); router.delete('/:id', controller.deleteUser);
export default router;
Error Handling Create centralized error handling middleware Place error handler at the top of the middleware stack Use custom error classes for different error types Never expose internal error details in production import { Middleware } from 'koa';
class AppError extends Error { constructor( public status: number, message: string, public expose: boolean = true ) { super(message); } }
const errorHandler: Middleware = async (ctx, next) => { try { await next(); } catch (err) { const error = err as Error & { status?: number; expose?: boolean }; ctx.status = error.status || 500; ctx.body = { error: { message: error.expose ? error.message : 'Internal Server Error', ...(process.env.NODE_ENV === 'development' && { stack: error.stack }) } }; ctx.app.emit('error', err, ctx); } };
Request Validation Use koa-joi-router or Zod for validation Validate body, query, and params Return clear validation error messages Create reusable validation middleware import { z } from 'zod'; import { Middleware } from 'koa';
const createUserSchema = z.object({ name: z.string().min(1), email: z.string().email(), });
const validate = (schema: z.ZodSchema): Middleware => { return async (ctx, next) => { try { ctx.request.body = schema.parse(ctx.request.body); await next(); } catch (error) { if (error instanceof z.ZodError) { ctx.status = 400; ctx.body = { errors: error.errors }; return; } throw error; } }; };
Authentication Implement JWT authentication with koa-jwt Store authenticated user in ctx.state.user Create authorization middleware for role-based access import jwt from 'koa-jwt';
app.use(jwt({ secret: process.env.JWT_SECRET }).unless({ path: [/^\/public/] }));
const requireRole = (role: string): Middleware => { return async (ctx, next) => { if (ctx.state.user?.role !== role) { ctx.throw(403, 'Forbidden'); } await next(); }; };
Security Use koa-helmet for security headers Implement rate limiting with koa-ratelimit Enable CORS with @koa/cors Validate and sanitize all inputs Use HTTPS in production Testing Use Jest or Mocha for testing Use supertest with app.callback() for integration tests Test middleware in isolation Mock context for unit tests import request from 'supertest'; import app from '../app';
describe('GET /api/users', () => { it('should return users list', async () => { const response = await request(app.callback()) .get('/api/users') .expect(200);
expect(response.body).toBeInstanceOf(Array);
}); });
Performance Use koa-compress for response compression Implement caching with koa-redis-cache Use connection pooling for databases Implement pagination for list endpoints Consider koa-static for serving static files Environment Configuration Use dotenv for environment variables Validate required environment variables at startup Create separate configs for different environments Never commit secrets to version control