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.
any type - create necessary types insteadsrc/
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
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`);
};
async/await for middlewareawait next() to pass control to downstream middlewareawait next() runs during the "upstream" phasectx.state to pass data between middlewareimport { ParameterizedContext, Middleware } from 'koa';
interface AppState {
user?: User;
requestId: string;
}
type AppContext = ParameterizedContext<AppState>;
const authMiddleware: Middleware<AppState> = async (ctx, next) => {
ctx.state.user = await validateToken(ctx.headers.authorization);
await next();
};
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;
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;
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);
}
};
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;
}
};
};
ctx.state.userimport 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();
};
};
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);
});
});
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).