Backend Development 19 min read

Comprehensive Guide to Building Web Applications with Koa

This tutorial walks you through setting up a Node.js environment, installing Koa, and progressively building a web application by covering basic HTTP services, context handling, response types, routing, middleware, error handling, cookies, form processing, and file uploads with clear code examples.

Architecture Digest
Architecture Digest
Architecture Digest
Comprehensive Guide to Building Web Applications with Koa

Koa is a lightweight, elegant web framework for Node.js that follows the Unix philosophy by keeping the core small and extending functionality through plugins. This guide starts from zero, showing how to prepare the environment, check Node version (>=7.6), clone the demo repository, and install dependencies.

Basic Usage

With just three lines of code you can create an HTTP server:

// demos/01.js
const Koa = require('koa');
const app = new Koa();

app.listen(3000);

Running node demos/01.js starts the server, which initially returns "Not Found" because no response body is set.

The Context object (available as ctx ) gives access to request and response. Setting ctx.response.body defines the output:

// demos/02.js
const Koa = require('koa');
const app = new Koa();

const main = ctx => {
  ctx.response.body = 'Hello World';
};

app.use(main);
app.listen(3000);

Koa can return different content types by inspecting ctx.request.accepts and setting ctx.response.type :

// demos/03.js
const main = ctx => {
  if (ctx.request.accepts('xml')) {
    ctx.response.type = 'xml';
    ctx.response.body = '
Hello World
';
  } else if (ctx.request.accepts('json')) {
    ctx.response.type = 'json';
    ctx.response.body = { data: 'Hello World' };
  } else if (ctx.request.accepts('html')) {
    ctx.response.type = 'html';
    ctx.response.body = '
Hello World
';
  } else {
    ctx.response.type = 'text';
    ctx.response.body = 'Hello World';
  }
};

Templates can be served by streaming a file:

// demos/04.js
const fs = require('fs');

const main = ctx => {
  ctx.response.type = 'html';
  ctx.response.body = fs.createReadStream('./demos/template.html');
};

Routing

Simple routing can be done by inspecting ctx.request.path :

// demos/05.js
const main = ctx => {
  if (ctx.request.path !== '/') {
    ctx.response.type = 'html';
    ctx.response.body = '
Index Page
';
  } else {
    ctx.response.body = 'Hello World';
  }
};

For more convenient routing, the koa-route module is used:

// demos/06.js
const route = require('koa-route');

const about = ctx => {
  ctx.response.type = 'html';
  ctx.response.body = '
Index Page
';
};

const main = ctx => {
  ctx.response.body = 'Hello World';
};

app.use(route.get('/', main));
app.use(route.get('/about', about));

Static assets are served with koa-static :

// demos/12.js
const path = require('path');
const serve = require('koa-static');

const main = serve(path.join(__dirname));
app.use(main);

Redirects are performed via ctx.response.redirect() :

// demos/13.js
const redirect = ctx => {
  ctx.response.redirect('/');
  ctx.response.body = '
Index Page
';
};

app.use(route.get('/redirect', redirect));

Middleware

Logging can be added as a middleware function:

// demos/07.js
const main = ctx => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  ctx.response.body = 'Hello World';
};

Middleware can be composed and chained, with the next() function passing control downstream. An example of a middleware stack demonstrates the first‑in‑last‑out execution order:

// demos/09.js
const one = (ctx, next) => { console.log('>> one'); next(); console.log('<< one'); };
const two = (ctx, next) => { console.log('>> two'); next(); console.log('<< two'); };
const three = (ctx, next) => { console.log('>> three'); next(); console.log('<< three'); };

app.use(one);
app.use(two);
app.use(three);

Asynchronous middleware uses async and await for operations like reading files:

// demos/10.js
const fs = require('fs.promised');
const Koa = require('koa');
const app = new Koa();

const main = async function (ctx, next) {
  ctx.response.type = 'html';
  ctx.response.body = await fs.readFile('./demos/template.html', 'utf8');
};

app.use(main);
app.listen(3000);

Multiple middleware can be combined with koa-compose :

// demos/11.js
const compose = require('koa-compose');

const logger = (ctx, next) => { console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`); next(); };
const main = ctx => { ctx.response.body = 'Hello World'; };

const middlewares = compose([logger, main]);
app.use(middlewares);

Error Handling

Errors can be thrown with ctx.throw(500) or by setting ctx.response.status = 404 . Centralized error handling middleware catches exceptions and formats the response:

// demos/16.js
const handler = async (ctx, next) => {
  try { await next(); } catch (err) {
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.body = { message: err.message };
  }
};

const main = ctx => { ctx.throw(500); };

app.use(handler);
app.use(main);

The error event can also be listened to for logging:

// demos/17.js
const main = ctx => { ctx.throw(500); };
app.on('error', (err, ctx) => console.error('server error', err));

When errors are caught manually, the event must be emitted explicitly with ctx.app.emit('error', err, ctx) :

// demos/18.js
const handler = async (ctx, next) => {
  try { await next(); } catch (err) {
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.type = 'html';
    ctx.response.body = '
Something wrong, please contact administrator.
';
    ctx.app.emit('error', err, ctx);
  }
};

Web Application Features

Cookies are accessed via ctx.cookies :

// demos/19.js
const main = function(ctx) {
  const n = Number(ctx.cookies.get('view') || 0) + 1;
  ctx.cookies.set('view', n);
  ctx.response.body = n + ' views';
};

Form data can be parsed with koa-body and validated:

// demos/20.js
const koaBody = require('koa-body');

const main = async function(ctx) {
  const body = ctx.request.body;
  if (!body.name) ctx.throw(400, '.name required');
  ctx.body = { name: body.name };
};

app.use(koaBody());

File uploads are also handled by koa-body with multipart support:

// demos/21.js
const os = require('os');
const path = require('path');
const koaBody = require('koa-body');

const main = async function(ctx) {
  const tmpdir = os.tmpdir();
  const filePaths = [];
  const files = ctx.request.body.files || {};
  for (let key in files) {
    const file = files[key];
    const filePath = path.join(tmpdir, file.name);
    const reader = fs.createReadStream(file.path);
    const writer = fs.createWriteStream(filePath);
    reader.pipe(writer);
    filePaths.push(filePath);
  }
  ctx.body = filePaths;
};

app.use(koaBody({ multipart: true }));

The article concludes with reference links to the Koa workshop, kick‑off‑koa, and official Koa examples repositories.

backend developmentmiddlewareNode.jsFile UploadRoutingerror handlingKoacookies
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.