worl.co

I’m , a software developer and writer living in New York.

Building an API Backend with TypeScript and Express - Part One: Setup

TypeScript gives JavaScript developers a much-needed type-safe option for front-end development, but it works just as well in a Node environment. Node even includes many next-generation JavaScript features that older browsers don’t ship, so you won’t need any post-processing tools like Babel or Webpack/Browserify. In this tutorial I will assume you have a basic understanding of the command line and have a recent version of Node and NPM installed. I would also recommend using Visual Studio Code because it has good out-of-the-box support for TypeScript.

We’re going to build a basic backend API with Node and TypeScript. We’ll use the popular Express web framework for the basic building blocks. In future posts, we’ll expand into database persistence, code organization, advanced TypeScript features, and more. In this post, we’ll just cover getting our TypeScript environment set up and creating a “Hello World!” route with Express.

If you don’t already have TypeScript installed, you can install it with npm install -g typescript in a terminal. At the time of writing, the current stable version is 2.1.

Now we can get started! cd into wherever you’d like to store this project, and run mkdir ts-express; cd ts-express. Then run npm init to create the package.json file describing the application. You can leave the defaults as they are.

You now have all the of the basic scaffolding to get started with TypeScript. It works very similarly to any other Node project, and your general workflow will be the same as a regular JavaScript project. We can now install our dependencies from NPM.

npm install --save express body-parser

Because these are JavaScript libraries, we need TypeScript definition files to get the full benefit of type-checking when we use them. These definition files declare the module layout and exported types for each library. You can install the definitions for Express and the body-parser middleware like this:

npm install --save @types/express @types/body-parser

Now let’s write some TypeScript! Open up your editor of choice into the ts-express directory and start by creating a src directory. This directory will hold our TypeScript code. Later we’ll configure where the TypeScript compiler should output our code. Once you’ve created the src directory, create a new file called app.ts. We’re going to place all our code in this file to start with and learn more about code organization later.

As a base, we’ll start with a simple JavaScript version and slowly convert it to TypeScript. All valid JavaScript is valid TypeScript. If you have a legacy JavaScript project to convert, you can start by changing all the file extensions from js to ts and incrementally adding types until you’re satisfied. Any compiler errors about types when compiling this un-typed code are really more like warnings: the TypeScript compiler just keeps going.

// src/app.ts

var express = require('express');
var app = express();

app.get('/', function(req, res) {
  res.send('Hello World!');
});

app.listen(3000, function() {
  console.log('Example app listening on port 3000!');
});

This code starts an Express server, adds one root route, then starts listening on port 3000. That’s about as stripped-down as you can get. Now let’s compile it!

$ tsc src/app.ts

Obviously compiling all our files one at a time as we write more code is not pleasant, and we definitely don’t want our compiled JavaScript sitting next to our TypeScript files. The TypeScript compiler has a configuration file to let us fix that.

Here’s the tsconfig.json file I’m going to use for the remainder of this project. Put it in the root of the project.

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": false,
    "removeComments": true,
    "preserveConstEnums": true,
    "outDir": "build",
    "strictNullChecks": true,
    "sourceMap": true,
    "target": "es2015"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "**/*.spec.ts"
  ]
}

I’ve highlighted the important options. outDir tells the compiler to output our compiled code into a directory named build. Don’t worry about creating it, the compiler will do that for us. strictNullChecks forces us to be correct about knowing if any of our variables can be null, which will save you some annoying debug time later when something is unexpectedly null or undefined. target tells the compiler to compile our code into ES2015 JavaScript syntax. Other options include ES3 and ES5, which you would most likely use when targeting browsers because of their more limited feature set. In general, TypeScript tries to support as many previous versions as are reasonable and possible for their advanced features like async/await.

Now you can compile with tsc and, hopefully, not receive any errors. Your code will output into the build directory as specified in tsconfig.json. You can now run the code:

$ tsc
$ node build/app.js
Example app listening on port 3000!

That’s all for this section! The next section will explore TypeScript’s type-checking, defining different routes, and validation.

Now check out Part Two - Routing and Validation.

Building an API Backend with TypeScript and Express - Part Two: Validation

In Part One, we set up our TypeScript environment, created a “Hello World!” application with Express, and compiled it with the TypeScript compiler. In this section, we will add more routes and validate requests. For validation, we will leverage an experimental TypeScript feature: decorators.

In order to enable this feature, we need to modify our tsconfig.json file to add two lines like this:

{
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
  ...
}

The experimentalDecorators option enables decorators (which are an experimental feature, disabled by default) and emitDecoratorMetadata will add run-time type information to our JavaScript code which will power our validations. We also need to install a few dependencies to make this feature work:

npm install --save reflect-metadata class-validator json-typescript-mapper

The technical details of decorators aren’t complicated. They’re just functions that run “around” where they are declared, whether that’s on an entire class, a property, a method, or a method parameter.

Our use of decorators will be around validating requests our API receives in a declarative way. Here’s what it will look like in use:

class CreatePostRequest {
  @Length(1, 250)
  body: string = '';
}

We’re declaring the request to contain an object with one property (body) that is a string between 1 and 250 characters.

In order to make this easy to use, we’re going to create an Express middleware in src/validation.ts.

// src/validation.ts

import * as express from 'express';
import {deserialize, JsonProperty} from 'json-typescript-mapper';
import {Validator} from "class-validator";
import {ValidationError} from 'class-validator';

// Because all type information is erased in the compiled
// JavaScript, we can use this clever structural-typing
// work-around enabled by TypeScript to pass in a class
// to our middleware.
type Constructor<T> = {new(): T};

// This function returns a middleware which validates that the
// request's JSON body conforms to the passed-in type.
export function validate<T>(type: Constructor<T>): express.RequestHandler {
  let validator = new Validator();

  return (req, res, next) => {
    let input = deserialize(type, req.body);

    let errors = validator.validateSync(input);
    if (errors.length > 0) {
      next(errors);
    } else {
      req.body = input;
      next();
    }
  }
}

// This middleware handles the case where our validation
// middleware says the request failed validation. We return
// those errors to the client here.
export function validationError(err: Error, req, res, next) {
  if (err instanceof Array && err[0] instanceof ValidationError) {
    res.status(400).json({errors: err}).end();
  } else {
    next(err);
  }
}

We’ve really created two middlewares here: validate and validationError. Together, we will combine these two to automatically handle validation for us. We can use the new validation middleware like this back in app.ts

// src/app.ts
import * as express from 'express';
import {json} from 'body-parser';
import {validate, validationError} from './validation';
import {Length} from 'class-validator';

class CreatePostRequest {
  @Length(1, 250)
  body: string = '';
}

var app = express();

app.use(json());

app.post('/', validate(CreatePostRequest), function (req, res) {
  res.send(req.body.body);
});

app.use(validationError);

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

Compile and run this code again and we’ll try it out:

$ tsc
$ node build/src/app.js
'Example app listening on port 3000!'

In a new terminal window, we can use cURL to send a request. We’re going to post some JSON to the root route and it will be validated by our new middleware and CreatePostRequest class.

$ curl -X POST -H "Content-Type: application/json" -d '{"body":"Hello World!"}' http://localhost:3000/
"Hello World!"

You can also verify our validation is working by trying to send something else.

$ curl -X POST -H "Content-Type: application/json" -d '{"body":null}' http://localhost:3000/
{"errors":[{"target":{"body":null},"value":null,"property":"body","children":[],"constraints":{"length":"body must be longer than 1 characters"}}]}

You may have noticed in this post we didn’t litter all our code with a bunch of type names. TypeScript has type inference and structural typing. Type inference means the compiler can infer which types we mean even if they aren’t specified directly in the code. This convenience saves a lot of typing and will make your TypeScript code look a lot like JavaScript, if that’s what you want. You can specify the type names when you think it helps with readability. Structural typing means TypeScript can inspect the structure of an object and use that structure in place of a type name. For example:

class Foo {
  bar: string;
}

let f = {bar: 'a string'};

function baz(foo: Foo) {
  console.log(foo.bar);
}

baz(f);

This code works just fine because f has the same structure as the Foo class.

That’s all for this section! We’ve successfully built a validation system that we can build on later.