# Web Frameworks
A backend web framework organizes the path from an HTTP request to a response. Node's HTTP API provides the transport; frameworks add conventions for middleware, routing, errors and response handling. They do not replace HTTP, and they are different from frontend UI frameworks or build tools such as Webpack.
| Framework | Main idea | Useful when |
|---|---|---|
| Express (opens new window) | Minimal routing and middleware with a large ecosystem | You want a small server with explicit routes |
| Koa (opens new window) | Async middleware around a shared request context | You want to understand and control the request pipeline |
| NestJS (opens new window) | Modules, controllers and dependency injection | A larger application benefits from a shared architecture |
# A request through Koa
This standalone server.cjs example uses Koa's async middleware API, available in Koa 2 and 3. It assumes koa is installed in the example application's dependencies. Run it with node server.cjs and request http://localhost:3000/api/students.
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
ctx.status = 500;
ctx.body = { error: 'Internal server error' };
ctx.app.emit('error', error, ctx);
}
});
app.use(async (ctx, next) => {
const started = Date.now();
console.log('before handler');
await next();
ctx.set('X-Response-Time', `${Date.now() - started}ms`);
console.log('after handler');
});
app.use(ctx => {
if (ctx.method !== 'GET' || ctx.path !== '/api/students') return;
console.log('handler');
ctx.body = [{ id: 1, name: 'Ada' }];
});
app.listen(3000, '127.0.0.1');
ctx is the context for one request: method and path identify the operation, ctx.body supplies the response, and ctx.status can set its status code. Koa serializes the array as JSON and normally sets status 200 when a body is supplied. An unmatched request remains a 404 in this example.
# Middleware runs downstream and back upstream
app.use registers middleware in order. Calling await next() transfers control to the next middleware and waits for it before continuing:
request → error boundary → timing middleware → handler
response ← error boundary ← timing middleware ← handler
before handler
handler
after handler
This is often called the onion model. With two wrappers A and B, the sequence is A before → B before → handler → B after → A after. Timing, logging and error handling can therefore surround the handler rather than being repeated in every route.
- Omitting
next()short-circuits the pipeline; later middleware will not run. This is useful for a completed response or a rejected request. - Calling
next()withoutawaitor returning its Promise does not wait for downstream work; timing and error handling can then be incorrect. - Put the error boundary before middleware whose failures it must catch. Log diagnostics on the server; do not send stack traces to clients.
Authentication is another middleware responsibility, but a hardcoded secret in a query string is not a production authentication scheme: URLs can be recorded in logs and history. It is deliberately not part of this minimal server.
# API responses and frontend files
An API route returns data; a static-file handler serves assets such as HTML, CSS and the generated JavaScript bundle. Koa itself does not include a router, body parser or static-file server: these require explicit middleware or a separate server. Use a maintained static-serving solution with a fixed asset directory, not unrestricted reads of request-supplied filesystem paths.
A development proxy can let the frontend call this API during development. Production needs an actual API server and an explicit way to serve the built assets; Webpack is not that backend.
Check your understanding: what happens if the handler never calls next()? Why can the timing middleware still execute its final statements?
Continue with TypeScript. Full API reference: Koa documentation (opens new window).