Node, Express, and TypeScript

Introduction

This tutorial will show you how to create a Node, Express, and TypeScript API in 8 easy steps.

It’s quick and easy to create an API using Node and Express. What’s not so great is that Node does not support the great features in the latest versions of JavaScript and TypeScript. Let’s fix that!

Requirements:

  • Node installed.
  • Familiarity with Node, TypeScript, and Express.

You can clone the GitHub repository for this tutorial here.

Step 1: Create Project and Add Dependencies

  • Start by creating a project directory and name it my-api.
  • Open the project directory in your favorite code editor, such as VS Code.
  • Create a new directory at the project root and name it src. All of your application code will live in this directory. We’ll add files to it later.
  • Create a new file at the project root and name it package.json. This is the npm configuration file which is the heart of the project. It contains references to project dependencies, utilities, utility configurations, and command scripts. Learn more about npm here.
{
    "name": "my-api",
    "version": "0.1.1",
    "license": "ISC",
    "dependencies": {
        "express": "4.18.2",
        "morgan": "^1.10.0"
    },
    "devDependencies": {
        "@types/express": "4.17.14",
        "@types/jest": "27.5.1",
        "@types/morgan": "^1.9.4",
        "@types/supertest": "^2.0.12",
        "cross-env": "^7.0.3",
        "dotenv": "^16.0.3",
        "jest": "28.1.0",
        "nodemon": "2.0.16",
        "supertest": "^6.3.3",
        "ts-jest": "28.0.3",
        "ts-loader": "9.3.0",
        "ts-node": "10.8.0",
        "typescript": "4.6.4",
        "webpack": "5.72.1",
        "webpack-cli": "4.9.2"
    }
}

Open a terminal in the context of the project folder (VS Code has a built-in terminal, View > Terminal) and enter the following command to download the dependencies:

npm install

After the download is complete, the project will contain a new file, package-lock.json, which is used internally by npm to lock down dependency versions, and a new directory, node_modules which contains the downloaded dependencies.

Step 2: Configure TypeScript

Create a new file at the project root and name it tsconfig.json. This file contains configuration settings for how TypeScript should transpile your code. Learn more about TypeScript configuration here.

{
    "compilerOptions": {
        "target": "es2016",
        "module": "commonjs",
        "resolveJsonModule": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
    }
}

This configuration tells TypeScript to transpile your TypeScript code into ES2016 JavaScript using CommonJS module syntax, perfect for use on a Node server.

Step 3: Configure Webpack

We’re going to use Webpack to build the application for production. It tells TypeScript where it should look for code to transpile, and where the transpiled code should be output. WebPack has many other great features for managing client side code, but for this application, we are just using it for transpiling the code. Learn more about Webpack here.

Create a new file at the project root and name it webpack.config.js. This file contains Webpack configuration settings.

const path = require('path');

module.exports = {
    target: 'node',
    mode: 'production',
    entry: path.resolve(__dirname, 'src', 'server'),
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'index.js',
    },
    resolve: {
        extensions: ['.ts', '.js'],
    },
    module: {
        rules: [
            {
                test: /\.(ts|js)?$/,
                exclude: /node_modules/,
                include: path.resolve(__dirname, 'src'),
                use: 'ts-loader',
            },
        ],
    },
    stats: {
        errorDetails: true,
    },
};

This configuration tells TypeScript to transpile .js and .ts files located in the /src directory and output the transpiled code into a /build directory.

Step 4: Configure Jest

Jest is a popular test runner and test framework. I’m adding this step to the tutorial only because the Jest configuration file requires a few tweaks when using it with TypeScript. Learn more about Jest here.

Create a new file at the project root and name it jest.config.ts. This file contains Jest configuration settings.

export default {
    collectCoverageFrom: [
        'src/**/*.{ts,js}',
        '!**/*.d.ts',
        '!**/node_modules/**',
    ],
    coverageDirectory: 'coverage',
    preset: 'ts-jest',
};

This configuration tells Jest to use the ts-jest TypeScript plugin, and to only use the /src directory for test coverage.

Step 5: Configure Nodemon

Nodemon is a handy utility that starts the application in a dev environment using the ts-node TypeScript plugin. It automatically restarts the application every time you save a watched file, so you can see the results of code changes immediately. Learn more about Nodemon here. Learn more about ts-node here.

Open the existing package.json file and add the following configuration settings.

{
    //   "webpack-cli": "4.9.2"
    // },
    // previous configuration settings 

    "nodemonConfig": {
        "watch": "./src",
        "ext": "ts, js, json"
    }
}

This configuration instructs Nodemon to watch for changes to .ts, .js, and .json files in the /src directory.

Review

Your project directory should look like this:

my-api
  |-node_modules
  |-src
  |-jest.config.ts
  |-package-lock.json
  |-package.json
  |-tsconfig.json
  |-webpack.config.js

Step 6: Create Express API Module

We’ll make a teeny, tiny, Express API as a proof of concept. You can add real functionality later. Just make sure all of your application code lives in the /src directory.

Create a new file in the /src directory and name it app.ts.

import express, { Application } from 'express';
import morgan from 'morgan';

// initialize express service
const app: Application = express();

// middleware
app.use(morgan('dev'));

// routes
app.get('/', (req, res) => res.send('Hello World'));
app.get('/foo', (req, res) => res.json({ bar: 'This API is lit!' }));

export default app;

Notice that we are using TypeScript syntax! We’ve initialized an instance of Express, and defined two API endpoints. The first endpoint returns a hard-coded string, the second endpoint returns a hard-coded JSON object. We’ve also added the morgan logging middleware. It outputs useful information to the terminal when testing the app.

Step 7: Create HTTP Server Module

This module is responsible for initializing the web server. It’s good practice to initialize the server in a dedicated module like this. It makes it easier to test Express endpoints, and it allows you to easily add other non-Express services in the future.

Create a new file in the /src directory and name it server.ts.

import http from 'http';
import app from './app';

// initialize http server and bind express api
const server = http.createServer(app);

// start server
const port = process.env.PORT || '8080';
server.listen(port, () => console.log(`Server started on port ${port}`));

Here we initialize and start an http server and bind our Express API to it.

Step 8: Add Command Scripts

Last, but not least, we need to add command scripts. These scripts allow us to easily build, test, and start our application.

Open the existing package.json file and add the following scripts.

{
    //   "ext": "ts, js, json"
    // },
    // previous configuration settings 

    "scripts": {
        "start": "node ./build/index",
        "dev": "nodemon ./src/server",
        "build": "webpack",
        "test": "jest"
    }
}

We’ve added four scripts, one that builds the application, one that starts the built application, one that starts the application for local development work, and one that runs our test suites (which I don’t cover in this tutorial, but when you add some tests, you’ll have a script ready).

Fire it Up!

Let’s use the scripts that we just created and see if this thing actually works.

  • First, try the build script. Enter the following command in the terminal. This should result in the creation of a /build directory containing the built application.

npm run build

  • Next, try the start script. This starts the built, production application. Enter the following command in the terminal.

npm start

  • You should see Server started on port 8080 in your terminal.
  • Open your browser, and navigate to this path.

http://localhost:8080

  • You should see Hello World in tiny letters.
  • Now try this path.

http://localhost:8080/foo

  • The browser should display the JSON object that you created on this route.
  • Now stop the server by pressing Ctrl + C in the terminal.
  • Lastly, try the dev script. Enter the following command in the terminal. This starts the development environment.

npm run dev

  • In your browser, navigate to this path again.

http://localhost:8080/foo

  • Once again, you’ll see the JSON object.
  • Let’s edit the object in our code. Open src/app.ts in your code editor.
  • Change the value of the bar property to the string ‘Hey, I made an edit‘ and save.
  • Now reload the browser page and you should see your change.

Conclusion

That wasn’t too bad was it? This is obviously a bare bones application. I only included dependencies that were absolutely necessary for the tutorial. When you add more dependencies, plugins, and tools to your application, they should work out-of-the-box and not require much fussing with the current configuration settings. Have fun!