Skip to content

Koa compatible router

As mentioned in the targets, Compas supports generating a Koa compatible router.

js
import { Generator } from "@compas/code-gen";

const generator = new Generator();

// Build your own structure or import an existing one

generator.generate({
	targetLanguage: "js",
	outputDirectory: "./src/generated",
	generators: {
		router: {
			target: {
				library: "koa",
			},
			exposeApiStructure: true,
		},
	},
});

Setup

js
import { getApp, createBodyParsers } from "@compas/server";
import { router } from "./src/generated/common/router.js";

// Includes logging, error handling, CORS handling and some more, returning a Koa
// application.
const app = getApp();

// Add your own middleware and custom route handlers
app.use((ctx, next) => {
	// ...
	return next();
});

// And finally use the generated router middleware
// We pass in a compatible body parser from @compas/server as well.
app.use(router(createBodyParsers()));

app.listen(3000);
js
import Koa from "koa";
import { router } from "./src/generated/common/router.js";

const app = new Koa();

// Add your own middleware and custom route handlers
app.use((ctx, next) => {
	console.log(ctx.method, ctx.path);
	return next();
});

// And finally use the generated router middleware
app.use(
	router({
		bodyParser: async (ctx) => {
			// use a library to parse the request body
		},
	}),
);

app.listen(3000);

All your routes are now be accessible but return a 405 Not Implemented HTTP status. Let's see how to add route implementations.

Implementing controllers

Implementing the controllers is done by overwriting their generated implementations.

js
// Note how we import the handlers or controller from the generated files
import { userHandlers } from "./src/generated/user/controller.js";

userHandlers.single = async (ctx) => {
	// ctx is a Koa context.
	// Set the response, like you'd normally do in Koa.
	ctx.body = {
		email: "my@email.com",
	};
};

import { imaginaryHandlers } from "./src/generated/imaginary/controller.js";

// Validators are integrated in to the generated router.
imaginaryHandlers.route = async (ctx) => {
	// `R.get("/:userId", "route").params({ userId: T.uuid() })`
	// `GET /e4dbc7dd-adfb-4fce-83f6-f5dcaf68c30c`
	// results in `ctx.validatedParams`. The router will automatically return a `400 Bad Request` if the `userId` is not a valid uuid.
	ctx.validatedParams.userId; // -> uuid

	// `R.get("/", "route").query({ offset: T.number().default(0) })`
	// `GET /?offset=30
	ctx.validatedQuery.offset; // -> number (30)

	// `R.post("/", "route").body({ tags: [T.string()] })`
	// Request body is validated as well
	ctx.validatedBody.tags; // -> string[]

	// `R.get("/", "route").response({ foo: "bar" })`
	// Responses are validated as well. This ensures that you as the API creator adhere to the structure (or contract).
	ctx.body = {}; // throws a `500 Internal Server Error`, response did not pass validators
	ctx.body = { foo: "bar" }; // returns a `200 OK`.
};

Integrate with other Compas features

Compas provides a few features to make working with Koa and Compas a more integrated experience. Take a look at for example session handling or file handling.