DEV Community

Uroš Štok
Uroš Štok

Posted on • Originally published at urosstok.com

1

Typed routes in Express

While Express wasn't built with Typescript, there are type definitions available - @types/express. This adds typings for routes (specifically for this post, Request and Response).

I've looked around for ways of properly doing Request and Response types, and haven't found anything that works without breaking something else or being complicated. So here's how I usually implement typesafety into express routes.

Let's say we had an endpoint for adding a new user:

import express from "express";

const app = express();

app.post("/user", (req, res) => {
    req.body.name; // autocomplete doesn't work
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

This is pretty standard javascript, besides using ESM imports, there's no reason we need typescript for this. So let's add some types:

import express, {Request, Response} from "express";
...
app.post("/user", (req: Request, res: Response) => {
    req.body.name; // autocomplete doesn't work
});
Enter fullscreen mode Exit fullscreen mode

Note that this is what happens normally even if we don't specify the types, typescript infers the Request and Response type from the function automatically. So we didn't really do much here.

Request.body type

What if this endpoint needs some input body data? Currently when we type req.body autocomplete doesn't offer anything special. Let's change that.

We can pass an interface to the Request type parameter list so that Typescript knows what variables are available in req.body. It would look something like this:

type UserRequestBody = { name: string };
app.post("/user", (req: Request<{}, {}, UserRequestBody>, res: Response) => {
    req.body.name; // autocomplete works
});
Enter fullscreen mode Exit fullscreen mode

We need to put {} for the first two parameters as the thing we want (body) is actually the third type parameter. As we can see in the Request definition:

interface Request<
        P = core.ParamsDictionary,
        ResBody = any,
        ReqBody = any, // this is the Request.body
        ...
Enter fullscreen mode Exit fullscreen mode

Now this is quite chunky code for simply passing an interface for the request body. Luckily there's a better way, we simply define a helper type:

type RequestBody<T> = Request<{}, {}, T>;
Enter fullscreen mode Exit fullscreen mode

With our cleaner definition we can simply use:

type RequestBody<T> = Request<{}, {}, T>;

type UserRequestBody = { name: string };
app.post("/user", (req: RequestBody<UserRequestBody>, res: Response) => {
    req.body.name; // autocomplete works
});
Enter fullscreen mode Exit fullscreen mode

Other defintions

Now with our new found knowledge of how to write clean route typed code we can declare helper types for all our use cases!

// for .body
type RequestBody<T> = Request<{}, {}, T>;
// for .params
type RequestParams<T> = Request<T>;
// for .query
type RequestQuery<T> = Request<{}, {}, {}, T>;
// and so on... similarly for Response
Enter fullscreen mode Exit fullscreen mode

Multiple types

To cover everything, we need to be able to specify multiple types, for example .body and .params. We can do so by simply adding a new type:

type RequestBodyParams<TBody, TParams> = Request<TParams, {}, TBody>
Enter fullscreen mode Exit fullscreen mode

Typed example

Here's the full example from the start, now with typed routes:

import express, {Request, Resposne} from "express";

const app = express();

type RequestBody<T> = Request<{}, {}, T>;
type UserRequestBody = { name: string };
app.post("/user", (req: RequestBody<UserRequestBody>, res: Response) => {
    req.body.name; // autocomplete works
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Closing notes

That's it! This should allow you to create proper typed routes. The next step would be to add schema validation for these routes.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay