Using Typescript with hapi
Tags: typescript, hapi
Published: 2021-03-11 15:49:52

---
title: Using Typescript with hapi
published: true
date: 2021-03-11 14:21:09 UTC
tags: typescript,hapi
series: Using Hapi
canonical_url: https://www.solarwinter.net/using-typescript-with-hapi/
---

I've been using [hapi](https://hapi.dev) lately, and decided to start using Typescript at the same time. When I looked though there didn't seem to be a lot out there on using them both together. Here's what I learned.

I'm going to assume you have some degree of familiarity with Javascript, along with a basic understanding of what Typescript is.

If that's not the case then I can would definitely recommend reading [the MDN JavaScript tutorials](https://developer.mozilla.org/en-US/docs/WebJavaScript), followed by [the 5 minute Typescript introduction](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html).

I've chosen to use [yarn](https://yarnpkg.com/) in the examples below; if you're using [npm](https://docs.npmjs.com/) instead, just change `yarn add` to `npm install`.

I've included what you need to get the system up and running, and tried to explain as we go, but this post isn't going to go in depth into any particular point. I've tried to include relevant links as we go, but if I've missed something out then definitely let me know and I'll try to cover it further.

[Part 2](https://dev.to/arafel/more-hapi-and-typescript-routes-2lhj)

## Creating the initial server

### Admin stuff

First thing - create a project directory and `package.json` file. Then install [hapi](https://hapi.dev/) for production, since we know we're going to need that.

```bash
$ mkdir hapi_typescript
$ cd hapi_typescript
$ yarn init -y
$ yarn add @hapi/hapi
```

We're also going to need a few more packages in development. Here we're adding the typescript system itself, along with type definitions for hapi and node.

Without installing those, Typescript doesn't know any of the type details for Node or for hapi, so it will complain at something which isn't actually a problem.

We want to be able to see the actual problems, as well as benefit from type-safety using Node and hapi. The type definitions in this post are all coming from [DefinitelyTyped](https://definitelytyped.org/).

```bash
$ yarn add -D typescript @types/hapi__hapi @types/node
$ yarn add -D nodemon npm-run-all
```

Next, we have to create a `tsconfig.json` file with the Typescript options. The easiest thing to do is let `tsc` do that for us. (`tsc` is the Typescript compiler executable.)

```bash
$ npx tsc --init
```

For this basic system we're only going to adjust two options - `outDir` (where to put the compiled Javascript) and `rootDir` (where the source code lives):

```json
    "outDir": "./lib",
    "rootDir": "./src",
```

And the final bit of admin - we'll add these scripts to the `package.json`file, to make it easy to develop.

- `dev:tsc` starts the compiler in [watch mode](https://www.typescriptlang.org/docs/handbook/compiler-options.html), meaning it watches for any changes and automatically rebuilds.
- `dev:serve` uses [`nodemon`](https://nodemon.io/) to automatically reload the server when the Javascript changes.
- `dev` uses [`npm-run-all`](https://github.com/mysticatea/npm-run-all) to run both commands at the same time, so you don't have to have two terminals open.

```json
    "scripts": {
        "dev:tsc": "tsc --watch -p .",
        "dev:serve": "nodemon -e js -w lib lib/main.js",
        "dev": "run-p dev:*"
    }
```

Lastly, we can change the line in `package.json` which tells node which file to execute. The scripts don't use it, but it's good practice.

```json
"main": "lib/main.js"
```

### Code!

Right - now we can finally do some coding! We’re going to separate the file containing the server setup code from the file which actually starts it up. It’ll pay off when we start adding tests.

Notice the type declarations for the variables, and the return types on the functions. If you’re going to use Typescript, you might as well benefit from the compiler catching type mistakes.

`src/server.ts`:

```ts
'use strict';

import Hapi from "@hapi/hapi";
import { Server } from "@hapi/hapi";

export let server: Server;

export const init = async function(): Promise<Server> {
    server = Hapi.server({
        port: process.env.PORT || 4000,
        host: '0.0.0.0'
    });

    // Routes will go here

    return server;
};

export const start = async function (): Promise<void> {
    console.log(`Listening on ${server.settings.host}:${server.settings.port}`);
    return server.start();
};

process.on('unhandledRejection', (err) => {
    console.error("unhandledRejection");
    console.error(err);
    process.exit(1);
});
```

`src/main.ts`:

```ts
import { init, start } from "./server";

init().then(() => start());
```

### Giving it a spin

Rather than using `yarn dev` here, let’s try the stages individually.

```bash
$ yarn dev:tsc
[19:54:59] Starting compilation in watch mode...

[19:55:00] Found 0 errors. Watching for file changes.
```

Well, that looks promising. Ctrl-C out of that and start the server:

```bash
$ yarn dev:serve
yarn run v1.22.10
$ nodemon -e js -w lib lib/main.js
[nodemon] 2.0.7
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): lib/**/*
[nodemon] watching extensions: js
[nodemon] starting `node lib/main.js`
Starting server, listening on 0.0.0.0:4000
```

It’s alive! It’s **alive**!

## Next step

It’s alive, but it doesn't exactly do much. Let's listen for a request and send a reply.

### Tests

Since we’re adding functionality, let’s add a test. (Tests are good for the usual reasons; this isn't the place to convince you you need them.)

#### Setup

We're going to keep our tests in `test`. We'll use [`chai`](https://www.chaijs.com/) and [`mocha`](https://mochajs.org/) to run them; since we're using Typescript we'll also want to add the relevant type annotations from DefinitelyTyped.

```bash
$ mkdir test
$ yarn add -D chai mocha @types/chai @types/mocha
```

Lastly, we'll use `ts-node` to directly run these, rather than compiling them to JavaScript.

```bash
$ yarn add -D ts-node
```

We’ll need to tweak `tsconfig.json` slightly, to tell `tsc` not to try building the tests. Add this after the end of the `compilerOptions` object.

```json
"exclude": [
    "test"
]
```

For the final bit of admin, we’ll add this script to `package.json` to make it easy to run the tests.

```json
"test": "NODE_ENV=test mocha -r ts-node/register test/**/*.test.ts"
```

#### Actual test code!

Finally, time to add our first test!

`test/index.test.ts`:

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

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

describe("smoke test", async () => {
    let server: Server;

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

    it("index responds", async () => {
        const res = await server.inject({
            method: "get",
            url: "/"
        });
        expect(res.statusCode).to.equal(200);
        expect(res.result).to.equal("Hello! Nice to have met you.");
    });
})
```

First thing is to import the various types we’re going to use. Then we import the `init` function from the `server.ts` file.

The `beforeEach` function creates a clean server object before each test, which the `afterEach` function cleans up. Separating the server code pays off now - we can initialise the server for use in our tests without actually having the overhead of starting it up.

The test itself makes use of the hapi [`inject`](https://hapi.dev/api/?v=20.1.0#-await-serverinjectoptions)method, calling into the server code without actually having to go to the effort of making an HTTP call. Here we’re hitting the `/` route; you can see the test expects the call to succeed, with a nice little message.

Of course, we haven’t written that code yet, so if we run it…

```bash
  0 passing (37ms)
  1 failing

  1) smoke test
       index responds:

      AssertionError: expected 404 to equal 200
      + expected - actual

      -404
      +200

      at hapi-typescript/test/index.test.ts:22:35
      [trace continues]
```

As expected, it fails. Let’s fix that.

### Server code

We’re back in `server.ts` now. First thing is to modify the type imports at the top of the file, to include the `Request` type:

```ts
import { Request, Server } from "@hapi/hapi";
```

We’ll need that for the `index` function which does the actual replying for us. In this case it simply returns a string, so we’ve declared that as the return type. Declaring the type of `request` makes sure we don’t use any properties or methods which aren’t present on that object.

```ts
function index(request: Request): string {
    console.log("Processing request", request.info.id);
    return "Hello! Nice to have met you.";
}
```

Lastly, we need to connect up the route in the `init()` function, where the comment in the earlier code indicates.

```ts
    server.route({
        method: "GET",
        path: "/",
        handler: index
    });
```

Now if we run the tests…

```bash
  smoke test
    ✓ index responds

  1 passing (33ms)

✨ Done in 2.25s.
```

Hurrah! Happiness ensues!

Well, that's one route defined in the same file. See [the next post](https://dev.to/arafel/more-hapi-and-typescript-routes-2lhj) to import routes from another file (with appropriate tests).