worl.co

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

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.