Extracting Routes and Config


We have our small Koa server up and running, and life is - I'm sure you will agree - really rather splendid.

However, we've done the bare minimum to get things off the ground. And that means we have stuff all over the shop. Well, all over one file, anyway.

Let's extract out where possible. We will start by moving our route into its own separate directory, and file.

mkdir src/routes
touch src/routes/healthcheck.ts

We will extract out the Router instantiation and declaration, and then make sure we export this code.

import Router from "koa-router";
const router = new Router();

router.get(`/ping`, async (ctx) => {
  try {
    ctx.body = {
      status: "success",
      data: "pong"
    };
  } catch (err) {
    console.error(err);
  }
});

export default router;

We still need to use our routes from the router in our Koa application's stack of middleware.

Previously, when things were all on in one file we had the line:

app.use(router.routes());

But that won't work anymore, as we've just moved the router code out of the server.ts file.

In order for this to continue to work, we need to require our healthcheck routes inside the server.ts file. Let's update the code:

// server.ts

import Koa from "koa";
import bodyParser from "koa-bodyparser";
import cors from "koa2-cors";
import logger from "koa-logger";

// this is new:
import healthcheckRoutes from "./routes/healthcheck";

const app = new Koa();

const PORT = process.env.PORT || 7654;

app.use(bodyParser());
app.use(
  cors({
    origin: "*"
  })
);
app.use(logger());

// this is updated:
app.use(healthcheckRoutes.routes());

const server = app
  .listen(PORT, async () => {
    console.log(`Server listening on port: ${PORT}`);
  })
  .on("error", err => {
    console.error(err);
  });

export default server;

Things should still work. A GET request to http://0.0.0.0:7654/ping should result in:

{
    "status": "success",
    "data": "pong"
}

Which is great, I'm sure you will agree.

Extracting Configuration

Another great candidate for extraction is any configuration.

What works in development is not going to cut it in production. And tracking down all the different places you have set a configuration value can be a real pain in the arse when ready to go live.

A better way (imo) is to put the configuration into a single object.

This object can then be required / imported, wherever necessary.

touch src/config.ts

And into that file, let's move the PORT config:

export const config = {
  port: process.env.PORT || '7654',
};

Again, we can update the server.ts file to make use of this new object:

// other stuff removed

import { config } from "./config";
const PORT = config.port;

Whilst at this stage this seems like overkill, this pattern does pay dividends as your apps grow in size. At least, that has been my personal experience.

With a little bit of modularity in action, our next step will be to add in a sprinkling of TypeScript.

Episodes