@ianwremmel/tracks-boot

@ianwremmel/tracks-boot is a tiny module to start Express apps asynchronously

Usage no npm install needed!

<script type="module">
  import ianwremmelTracksBoot from 'https://cdn.skypack.dev/@ianwremmel/tracks-boot';
</script>

README

tracks-boot (@ianwremmel/tracks-boot)

license standard-readme compliant npm (scoped) npm

Dependabot badge semantic-release

CircleCI

@ianwremmel/tracks-boot is a tiny module to start Express apps asynchronously

Most tutorials for Express show how to get up and running quickly with a synchronous example. Unfortunately, the reality is that sometimes we need to do async work before we can start listening for connections. Maybe we need to load config from a file or wait for database connections. Maybe we're pulling config from a remote store like Consul. In any case, it can get pretty clunky. This module aims to streamline that clunkiness as much as possible.

Table of Contents

Install

npm install @ianwremmel/tracks-boot

Usage

tracks-boot exports two functions, prepare for doing all of the async work of preparing your application and boot for binding it to a port. These are separate functions so that you can use e.g. supertest against a prepare app without needing to test over http.

First, import tracks boot

import {boot, prepare} from '@ianwremmel/tracks-boot';

Next, create your app factory (yes, I said "factory"; it's not that bad. it's just a function).

export function createApp() {
    return prepare(async (app) => {
        // do all of your normal app setup here. add middleware, routes, etc.
        app.get('/ping', (req, res) => {
            res.send('It Works!');
        });

        // This is an async function, so you can use await anywhere you need to.
    });
}

Now, define your boot behavior. You could put this in the same file as your app factory and rely on testing require.main === module to only start the app when appropriate or you could use a separate file. The example below assumes everything goes in the same file. This example just confirms the environment is set correctly to bind to a port. (I recommend doing most other env-var checking in the app factory rather than here. PORT is checked here because it's explicitly needed by boot()).

if (require.main === module) {
    if (!process.env.PORT) {
        throw new Error('PORT is not defined');
    }

    const port = Number(process.env.PORT);

    if (Number.isNaN(port)) {
        throw new Error('PORT must be a number');
    }

    // we'll just pass console as our logger, but if you're using something more
    // complex like bunyan, that's fine too. Just make sure it implements
    // `info: (arg: string) => void`
    boot({logger: console, port}, create());
}

boot returns a Promise<http.Server>, so if you need to shut it down programmatically (vs killing the process), you can use e.g. boot(...).then((server) => server.close());

Finally, we can use supertest to test our app without binding it to a port

import {createApp} from '.';

it('responds to pings', async () =>
    supertest(await createApp())
        .get('/api/v1/healthcheck')
        .expect(200)
        .expect('It Works!'));

Maintainer

Ian Remmel

Contribute

PRs Welcome

License

MIT © Ian Remmel 2019 until at least now