Typed Routes with TypeScript

Although Esper started out as an OCaml shop, we also use quite a bit of TypeScript to build our front-end. “Why We Use TypeScript” would probably be a long post in and of itself, but this post is about how we use TypeScript to implement a small, open-source library that implements statically-typed route checking.

If you don’t care about the why or how, you can check out said library on Github or download the typed-routes package on NPM.


By routing, we mean taking some action based on a file-like string path. The path in question is usually a URL, and routing can occur on a web app backend, by the frontend for a single page app, or even by a mobile app.

Probably the most popular way of representing routes in JavaScript-land is an Express-style path (which is typically implemented using the path-to-regexp package). An Express-style path looks like this: /path/to/:arg1/:arg2. It matches/path/to/abc/123, and it captures the third and fourth parts of that path inside a param object that looks like: { arg1: "abc", arg2: "123" }.

This works well enough, but it’s not ideal from a static type-checking perspective. In TypeScript at least, /path/to/:arg1/:arg2 is just a string. There isn’t much it can (currently) do to parse out the parameters. Nor is there much it can do to verify that arg2 is an integer.

Chainable Route Definitions

But there’s nothing saying we have to use Express-style paths. Or even (just) strings at all. That’s where typed-routes comes in. With typed-routes, you can write your route with a chainable interface like this:

You can then convert string paths to param objects like this:

And you convert param objects back to string paths like this:

The library also features optional parameters and wildcard / rest routes as well:

Type All the Things

The actual implementation isn’t anything fancy, but the typing takes advantage of mapped types, a relevatively recent addition to TypeScript.

Here’s what the type for our param method looks like. For the sake of this post, these aren’t the exact types being used by typed-routes but they help illustrate what’s going on.

A few explanations:

Our Route interface is defined with a generic type describing how the parameters passed to this type look like. So Route means “a route with a single string param named arg1”.

K is a generic type indicating the key we want to use to describe a given parameter. TypeScript supports string literal types, so it’s capable of recognizing that K is of type "arg1" or "myKey" or whatever is passed to the function.

T is a generic type indicating what the type of that parameter should be. It defaults to string is not provided or inferred.ParamType is another interface for an object that describes how to convert a string path part to another type and back again. It looks like this:

typed-routes provides a few basic types such as IntParam. There isn’t an integer type in JavaScript though, so IntParamreally has a signature of ParamType, although its implementation code ensures that the parsed value is always an integer.

So when we call param("arg1", IntParam), TypeScript infers K to be "arg1" and T to be of type number.

The last bit to explain is the return value: Route

. This is where mapped types comes in. [X in K]assumes that K is a union of string literals like "key1"|"key2"|"key3" and maps each string literal to some value X. Combined with the keyof operator, this lets us create new types using the keys of other types:

In the immediate case though, we know that K is just a single string literal that we want to use as the name of a param with type T. That is, if K refers to "arg1" and T referes to number, then { [X in K]: T} means { arg1: number }.

We use the intersection operator (&) to unite the previous param type P with the new addition. Taken altogether, having a return type of Route

 means that TypeScript understands the param method to return a new Route some param K referring to type T added to the original types. We can then chain these calls together and create arbitrarily long routes with whatever types we want.

We do something similar to implement optional types and rest types.

Note that we extend Route from OptRoute and OptRoute from RestRoute because want TypeScript to understand that a required parameter cannot follow an optional param and that no params can follow a rest / remainder param.

Not All Batteries Included

typed-routes only implements parsing a string path to get a typed params object and stringifying the params object back to path. It doesn’t actually implement routing (i.e. doing something based on a path) itself. It’s more of an alternate to path-to-regexp than anthing else.

But an actual router implementation itself isn’t too tricky. Here’s what a simple hashchange-based router for an SPA might look like:

Trade Offs

When you build a brand new app with TypeScript, it’s very tempting to try to type-check everything. But with web apps at least, you generally have two things working against you: (1) TypeScript is a superset of JavaScript, which comes with a lot of things that are not statically typed, and (2) you’re dealing with user input and/or distributed systems, where at least some of those systems are not things you control.

As such, we’ve occasionally found ourselves bending over backwards to write our code in a way that makes it easier to catch errors with TypeScript, as opposed to simply writing idiomatic JavaScript. This generally takes the form of wrapper functions or objects that have no real purpose from JavaScript’s perspective, but allow us to pass along additional type information. It’s fair to say we might have done that with typed-routes, insofar that it’s a bit more verbose and not as performant as simply defining a bunch of regular expressions.

In Esper’s case though, we encode a quite a bit of information in the routes and paths we pass around our single page app. That information is visible (and easily modifiable) via the URL. The approach outlined here allows us to be reasonably confident that we’re not dealing with malformed data when pulling params from our URL, and that’s a win for us.

Thanks for reading. We hope typed-routes is useful for you. PRs and feature requests are welcome.

Maze navigation challenge

We’ll be running this friendly programming challenge continuously for hopefully many months or years. Keep your submissions coming!

At Esper we’re always looking to challenge ourselves in new ways. Some of us find that writing programs and running them is more fun than a bunch of other activities, including but not limited to watching tv and talking to people; although we sometimes do. Anyway. Maybe your current task is a bit boring and you’re looking for new ways to procrastinate. Seek no further, we’ve created this little programming challenge just for you. If you succeed, you’ll win this one amazing mug, which undoubtedly will make all your friends and colleagues jealous of your advanced computer hacking skills.



This is one mug, not two. Sorry, we will no longer ship the mug to destinations outside of the United States. We’re really sorry and we’re working on an alternate prize we can send to everyone.


You must write a program for a robot that navigates a closed maze in search for letters that were printed on the floor of the maze. The solution consists of the list of all letters, ordered alphabetically. The letters are capitals (A-Z) and the same letter may exist in multiple locations.


The solution for the following maze is AABQ:

#   A   #   #
# ## ## # # #
# Q# #   B  #
#  #A# # ####

# symbols indicate walls, which are illegal positions. Your robot can navigate on all other cells vertically or horizontally but not diagonally. You can see the 4 neighbors cells (up, down, left, right) around your current cell.

The initial position of the robot can be any non-wall cell (empty or letter), inside the maze.

Navigation API

Your program will read cell information from stdin, then write a command on stdout, and loop.

Cell information is given as one line of 5 characters indicating the type of the current cell and the type of each of the neighbor cells in the following order: current, up, down, left, right. In the example, if the current position is the position of the letter Q, cell information will be given as:

Q#  #

A command is either one of the 4 navigation commands indicating which neighbor cell to move to (U: up, D: down, L: left, R: right) or the key for escaping the maze starting with the K prefix, e.g. KAABQ which terminates the game. Commands are terminated by a newline character (LF).

An attempt to move into a wall or giving a wrong key terminates the life of the poor robot.

Here is a very simple maze:


A successful session for this maze, assuming a start position on cell marked with A is:

 input: A###B
output: R
 input: B##A#
output: KAB


The number of moves is limited to at most twice the number of moves required by our reference solution. This limit as well as the size of the maze remain undisclosed (edited 2015-03-17).

Your program may not consult files or network resources other than stdin and stdout as specified above. It must consist in human-readable source code written by yourself. We will build it and run it on Ubuntu 14.04 (64 bits). Please provide:

  • the list of packages needed to build and run your solution or your best guess
  • a shell script to build your solution in one shot
  • the command to run your solution


Make sure your solution is successful on the sample mazes on this page, and make sure to flush stdout after printing each command when your program runs non-interactively.


Please send us your solution to programming@esper.com. All successful entries win the exclusive trophy mug!

About us

We are Esper, a still small company based in the San Francisco Bay Area, and we’re building a service to empower executives with professional executive assistants 24/7. If you share our passion and standards in software engineering and want to make a difference in the world, you should talk to us!