3 min read

Hapi - using routes from external files

[Last time we left it], the server only had a single route and was all in one file. That’s fine to start with but it’s not exactly scalable. Let’s add some routes from external files.

Last time we left it, the server only had a single route and was all in one file. That’s fine to start with but it’s not exactly scalable. Let’s add some routes from external files.

We’ll go for something similar to the traditional “hello world”. We’ll have a route, /hello, which will say “Hello World”. We’ll also add a route “under” that, /hello/name, which will greet name.

The files we produce here are all in the Github repo.

Test it!

Of course since we’re adding new functionality, we know what comes first…

The good news is that we don’t have any setup at all to do this time, and we can just go right ahead with the test file.

test/hello.test.ts:

import { Server } from "@hapi/hapi";
import { describe, it, beforeEach, afterEach } from "mocha";
import { expect } from "chai";

import { init } from "../src/server";

describe("server greets people", async () => {
	let server: Server;

	beforeEach((done) => {
		init().then(s => { server = s; done(); });
	})
	afterEach((done) => {
		server.stop().then(() => done());
	});

	it("says hello world", async () => {
		const res = await server.inject({
			method: "get",
			url: "/hello"
		});
		expect(res.statusCode).to.equal(200);
		expect(res.result).to.equal("Hello World");
	});

	it("says hello to a person", async () => {
		const res = await server.inject({
			method: "get",
			url: "/hello/Tom"
		});
		expect(res.statusCode).to.equal(200);
		expect(res.result).to.equal("Hello Tom");
	});
})

The imports are much the same. In fact almost all of this is the same structure as the index test - the only real different is that we have two tests in one describe block.

Since they’re so similar, I’m going to assume that’s understandable and move on. Before we do though, just to check it fails:

  server greets people
    1) says hello world
    2) says hello to a person

  smoke test
    ✓ index responds


  1 passing (70ms)
  2 failing

Okay, that does exactly what we expect and fails. Onwards…

Code it!

Routes

Again - no setup work this time, just code. Diving straight in - we want to keep these routes in a separate file. Using the obvious name…

src/hello.ts:

import { Request, ResponseToolkit, ResponseObject, ServerRoute } from "@hapi/hapi";

async function sayHello(request: Request, h: ResponseToolkit): Promise<ResponseObject> {
  const name: string = request.params.name || "World";
  const response = h.response("Hello " + name);
  response.header('X-Custom', 'some-value');
  return response;
}

export const helloRoutes: ServerRoute[] = [
  {
    method: "GET",
    path: "/hello",
    handler: sayHello
  },
  {
    method: "GET",
    path: "/hello/{name}",
    handler: sayHello
  }
];

Okay, this one has a bunch more types we haven’t seen before. Ignoring the Request type we met last time, we’ve just met:

  • ResponseToolkit - if you’re not using Vision to render templates, the toolkit is the thing you’ll usually use to send your response down. You can generate a response as seen above,
  • ResponseObject is what the ResponseToolkit produces - seems reasonable, right? A lot of the stuff in there you can probably leave alone; you might need things like response.code to explicitly set a status code.
  • ServerRoute is exactly what it sounds like, a route defined on the server. The routes above are about as simple as you get - the HTTP method, the path, and the function to handle it.

In the route definitions, /hello/{name} tells hapi to capture the path component after /hello/ and place it in the request.params object.

That’s all you need to do, and here you start to see some of the differences between hapi and Express - body/request parsing is already included and handled, batteries are included, no plugins required. It makes it simple to use the same function to handle both routes.

Important note - in real life, do not simply take user parameters and echo them back. Use something like Hoek’s escapeHtml().

Server

Plugging these routes into the server is simple. Import the route array:

src/server.ts:

import { helloRoutes } from "./hello";

Add it to the server:

server.route(helloRoutes);

And that’s it, we should be done. Of course we have to know…

$ yarn test
yarn run v1.22.10
$ NODE_ENV=test mocha -r ts-node/register test/**/*.test.ts


  server greets people
    ✓ says hello world
    ✓ says hello to a person

  smoke test
    ✓ index responds


  3 passing (63ms)

✨  Done in 3.05s.

Perfect. :-)