Your First Node Express App with Typescript
Tags: node, typescript, javascript, webdev
Published: 2021-01-19 14:36:14

---
title: Your First Node Express App with Typescript
published: true
description: Express is the most ubiquitous framework for nodejs. In this post, we learn how to add Typescript to the mix.
tags: node, typescript, javascript, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/cnsretbjm4v55hcqc4qh.png
canonical_url: https://typeofnan.dev/your-first-node-express-app-with-typescript/
---

Express is the most ubiquitous framework for nodejs. In this post, we learn how to add Typescript to the mix.

# The Goal

Our goal here is to be able to use Typescript to develop our application quickly, but ultimately we want our application to compile down to plain old javascript to be executed by the nodejs runtime.

# Initial Setup

First and foremost, we'll want to create an application directory in which we host our app files. We'll call this directory `express-typescript-app`:

```bash
mkdir express-typescript-app
cd express-typescript-app
```

To accomplish our goal, we'll want to make a distinction between what we install as regular application dependencies versus development dependencies (i.e., dependencies that will help us develop our application but that won't be necessary after we compile our code).

Throughout this tutorial, I'll be using `yarn` as the package manager, but you could use `npm` just as easily!

## Production Dependencies

In production, this will still be an `express` app. Therefore, we'll need to install express!

```bash
yarn add express
```

Note that this will create a `package.json` file for us!

For now, this will be our _only_ production dependency (we'll add another later).

## Development Dependencies

In development, we'll be writing Typescript. Therefore, we need to install `typescript`. We'll also want to install the types for both express and node. We use the `-D` flag to let `yarn` know that these are dev dependencies.

```bash
yarn add -D typescript @types/express @types/express @types/node
```

Great! But we're not quite done. Sure, we could stop here, but the problem is that we would need to _compile our code_ every time we wanted to see changes in development. That's no fun! So we'll add a couple additional dependences:

- `ts-node`&mdash;this package will let us run Typescript without having to compile it! Crucial for local development.
- `nodemon`&mdash;this package automagically watches for changes in your application code and will restart your dev server. Coupled with `ts-node`, `nodemon` will enable us to see changes reflected in our app _instantaneously_!

Again, these are _development_ dependencies because they only help us with development and won't be used after our code is compiled for production.

```bash
yarn add -D ts-node nodemon
```

## Configuring our App to Run

### Configuring Typescript

Since we're using Typescript, let's set some Typescript options. We can do this in a `tsconfig.json` file.

```bash
touch tsconfig.json
```

Now in our Typescript config file, let's set some compiler options.

- `module: "commonjs"`&mdash;when we compile our code, our output will use `commonjs` modules, which we're familiar with if we've used node before.
- `esModuleInterop: true`&mdash;this option allows us to do star (\*) and default imports.
- `target: "es6"`&mdash;unlike on the front-end, we have control of our runtime environment. We will make sure we use a version of node that understands the ES6 standard.
- `rootDir: "./"`&mdash;the root directory for our Typescript code is the current directory.
- `outDir: "./build"`&mdash;when we compile our Typescript to JavaScript, we'll put our JS in the `./build` directory.
- `strict: true`&mdash;enables strict type-checking!

All together, our `tsconfig.json` file should look like this:

```json
{
  "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "rootDir": "./",
    "outDir": "./build",
    "strict": true
  }
}
```

### Configuring package.json Scripts

Currently, we have no `package.json` scripts! We'll want to add a couple scripts: one script to `start` the app in development mode and another script to `build` the application for production. To start the application in development mode, we just need to run `nodemon index.ts`. For building the application, we've given our Typescript compiler all the information it needs in the `tsconfig.json` file, so all we have to do is run `tsc`.

The following shows what your `package.json` file might look like at this point. Note that your dependencies will likely be at different versions than mine since I wrote this at some point in the past (hello from the past, by the way).

```json
{
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "@types/express": "^4.17.11",
    "@types/node": "^14.14.21",
    "nodemon": "^2.0.7",
    "ts-node": "^9.1.1",
    "typescript": "^4.1.3"
  },
  "scripts": {
    "build": "tsc",
    "start": "nodemon index.ts"
  }
}
```

## Git Config

If you're using git (I recommend it!), you'll want a `.gitignore` file to ignore your `node_modules` folder and your `build` folder:

```bash
touch .gitignore
```

And the file contents:

```
node_modules
build
```

## Finished Setup!

I hope you've made it this far because we're done setup! It's not _too_ bad, but definitely slightly more of a barrier to entry than a normal express.js application.

# Creating our Express App

Let's create our express app. This is actually fairly similar to how we would do it with plain old JavaScript. The one difference is that we get to use ES6 imports!

Let's create `index.ts`:

```bash
touch index.ts
```

And in the `index.ts` file, we can do a basic "hello world" example:

```javascript
import express from 'express';

const app = express();
const PORT = 3000;

app.get('/', (req, res) => {
  res.send('Hello world');
});

app.listen(PORT, () => {
  console.log(`Express with Typescript! http://localhost:${PORT}`);
});
```

Now in our terminal we can start the app by using `yarn run start`:

```bash
yarn run start
```

And you'll get an output like this:

```
$ nodemon index.ts
[nodemon] 2.0.7
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node index.ts`
Express with Typescript! http://localhost:3000
```

We can see `nodemon` is watching all our files for changes and launches our app using `ts-node index.ts`. We can now navigate to `http://localhost:3000` in a web browser and see our "hello world" app in all it's glory!

![browser showing "hello world"](https://dev-to-uploads.s3.amazonaws.com/i/jbu16f94gd4fhpy7ap1r.png)

Huzzah! (well, it's a start!)

# Beyond "Hello World"

Our "Hello world" app is a nice achievement, but I think we can do more. Let's create some (very bad) user registration functionality to flex our express/typescript muscles a bit. Specifically, this functionality will:

- Maintain a list of users and associated passwords _in memory_
- Have a `POST` endpoint that allows users to register (i.e., adds an additional user to the aforementioned list)
- Have a `POST` endpoint that allows users to attempt to sign in, issuing an appropriate response based on the correctness of provided credentials

Let's get started!

## Maintaining Users

First, let's create a `types.ts` file in which we can declare our `User` type. We'll end up using this file for more types in the future.

```bash
touch types.ts
```

Now add the `User` type in `types.ts` and make sure to _export_ it:

```typescript
export type User = { username: string; password: string };
```

Okay! So rather than using a database or anything fancy like that, we're just going to maintain our users in memory. Let's create a `users.ts` file in a new directory, `data`.

```bash
mkdir data
touch data/users.ts
```

Now in our `users.ts` file, we can create an empty array of users and make sure to specify it as an array of our `User` type.

```typescript
import { User } from "../types.ts;

const users: User[] = [];
```

## POSTing New Users

Next, we'll want to be able to `POST` a new user to our application. If you're familiar with what an HTTP actually looks like, you know that variables will typically come across in the HTTP request body looking something like url encoded variables (e.g., `username=foo&password=bar`). Rather than parsing this ourselves, we can use the ubiquitous `body-parser` middleware. Let's install that now:

```bash
yarn add body-parser
```

And then we'll import and use it in our app:

```typescript
import express from 'express';
import bodyParser from 'body-parser';

const app = express();
const PORT = 3000;

app.use(bodyParser.urlencoded({ extended: false }));

app.get('/', (req, res) => {
  res.send('Hello world');
});

app.listen(PORT, () => {
  console.log(`Express with Typescript! http://localhost:${PORT}`);
});
```

Finally, we can create a `POST` request handler on a `/users` endpoint. This handler will do a few things:

- Check if both a `username` and `password` are defined on the request body and run some very basic validations on those fields
- Return a `400` status message if there is anything wrong with the provided values
- Push a new user to our `users` array
- Return a `201` status message

Let's get to it. First, we create an `addUser` function in our `data/users.ts` file:

```typescript
import { User } from '../types.ts';

const users: User[] = [];

const addUser = (newUser: User) => {
  users.push(newUser);
};
```

Now, we go back to our `index.ts` file and add the `"/users"` route:

```typescript
import express from 'express';
import bodyParser from 'body-parser';
import { addUser } from './data/users';

const app = express();
const PORT = 3000;

app.use(bodyParser.urlencoded({ extended: false }));

app.get('/', (req, res) => {
  res.send('Hello world');
});

app.post('/users', (req, res) => {
  const { username, password } = req.body;
  if (!username?.trim() || !password?.trim()) {
    return res.status(400).send('Bad username or password');
  }
  addUser({ username, password });
  res.status(201).send('User created');
});

app.listen(PORT, () => {
  console.log(`Express with Typescript! http://localhost:${PORT}`);
});
```

Our logic here is simply that our `username` and `password` variables need to exist and, when using the `trim()` method, they need to be longer than zero characters. If those criteria fail, we return a `400` error with a custom Bad Request message. Otherwise, we `push` the new `username` and `password` onto our `users` array and send a `201` status back.

**Note:** You may notice that our array of users has no way of knowing if a username is added twice. Let's pretend our app doesn't have this glaring issue!

Let's take this signup logic for a test drive using `curl`! In your terminal, make the following POST request:

```bash
curl -d "username=foo&password=bar" -X POST http://localhost:3000/users
```

You should get the following response back:

```
User created
```

Success! Now, let's just verify that our request fails if we don't meet our validation criteria. We'll provide a password that's just one space character (`" ".trim()` is falsey so our validation will fail).

```bash
curl -d "username=foo&password= " -X POST http://localhost:3000/users
```

And we get the following response:

```
Bad username or password
```

Looking good to me!

## Logging In

Logging in will be a very similar process. We'll grab the provided `username` and `password` from the request body, use the `Array.find` method to see if that username/password combination exists in our `users` array, and return either a `200` status to indicate the user is logged in or a `401` status to indicate that the user is not authenticated.

First, let's add a `getUser` function to our `data/users.ts` file:

```typescript
import { User } from '../types';

const users: User[] = [];

export const addUser = (newUser: User) => {
  users.push(newUser);
};

export const getUser = (user: User) => {
  return users.find(
    (u) => u.username === user.username && u.password === user.password
  );
};
```

This `getUser` function will either return the matching `user` from the `users` array or it will return `undefined` if no users match.

Next, we use this `getUser` function in our `index.ts` file:

```typescript
import express from 'express';
import bodyParser from 'body-parser';
import { addUser, getUser } from "./data/users';

const app = express();
const PORT = 3000;

app.use(bodyParser.urlencoded({ extended: false }));

app.get('/', (req, res) => {
  res.send('Hello word');
});

app.post('/users', (req, res) => {
  const { username, password } = req.body;
  if (!username?.trim() || !password?.trim()) {
    return res.status(400).send('Bad username or password');
  }
  addUser({ username, password });
  res.status(201).send('User created');
});

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  const found = getUser({username, password})
  if (!found) {
    return res.status(401).send('Login failed');
  }
  res.status(200).send('Success');
});

app.listen(PORT, () => {
  console.log(`Express with Typescript! http://localhost:${PORT}`);
});
```

And now we can once again use curl to add a user, log in as that user, and then also fail a login attempt:

```bash
curl -d "username=joe&password=hard2guess" -X POST http://localhost:3000/users
# User created

curl -d "username=joe&password=hard2guess" -X POST http://localhost:3000/login
# Success

curl -d "username=joe&password=wrong" -X POST http://localhost:3000/login
# Login failed
```

Hey, we did it!

# Exporing Express Types

You may have noticed that everything we have done so far, outside of our initial setup, is basic express stuff. In fact, if you have used express a bunch before, you're probably bored (sorry).

But now we'll get a bit more interesting: we're going to explore some of the types exported by express. To do so, we will define a custom structure for defining our routes, their middleware, and handler functions.

## A Custom Route Type

Perhaps we want to establish a standard in our dev shop where we write all our routes like this:

```javascript
const route = {
  method: 'post',
  path: '/users',
  middleware: [middleware1, middleware2],
  handler: userSignup,
};
```

We can do this by defining a `Route` type in our `types.ts` file. Importantly, we'll be making use of some important types exported from the `express` package: `Request`, `Response`, and `NextFunction`. The `Request` object represents the request coming from our client, the `Response` object is the response that express sends, and the `NextFunction` is the signature of the `next()` function you may be familiar with if you have used express middlware.

In our `types.ts` file, let's specify our `Route`. We'll make liberal use of the `any` type for our `middleware` array and `handler` function since we will want to discuss those further later.

```typescript
export type User = { username: string; password: string };

type Method =
  | 'get'
  | 'head'
  | 'post'
  | 'put'
  | 'delete'
  | 'connect'
  | 'options'
  | 'trace'
  | 'patch';

export type Route = {
  method: Method;
  path: string;
  middleware: any[];
  handler: any;
};
```

Now, if you're familiar with express middleware, you know that the a typical middleware function looks something like this:

```javascript
function middleware(request, response, next) {
  // Do some logic with the request
  if (request.body.something === 'foo') {
    // Failed criteria, send forbidden resposne
    return response.status(403).send('Forbidden');
  }
  // Succeeded, go to the next middleware
  next();
}
```

It turns out that express exports types for each of the three arguments that middlware take: `Request`, `Response`, and `NextFunction`. Therefore, we _could_ create a `Middleware` type if we wanted to:

```typescript
import { Request, Response, NextFunction } from 'express';

type Middleware = (req: Request, res: Response, next: NextFunction) => any;
```

...but it turns out express has a type for this already called `RequestHandler`! I don't _love_ the name `RequestHandler` for this type, so we're going to go ahead and import it under the name `Middleware` and add it to our `Route` type in `types.ts`:

```typescript
import { RequestHandler as Middleware } from 'express';

export type User = { username: string; password: string };

type Method =
  | 'get'
  | 'head'
  | 'post'
  | 'put'
  | 'delete'
  | 'connect'
  | 'options'
  | 'trace'
  | 'patch';

export type Route = {
  method: Method;
  path: string;
  middleware: Middleware[];
  handler: any;
};
```

Finally, we need to type our `handler` function. This is _purely_ a personal preference since our handler could technically be our last middleware, but perhaps we have made a design decision that we want to single out our `handler` function. Importantly, we don't want our handler to take a `next` parameter; we want it to be the end of the line. Therefore, we will create our own `Handler` type. It will look very similar to `RequestHandler` but won't take a third argument.

```typescript
import { Request, Response, RequestHandler as Middleware } from 'express';

export type User = { username: string; password: string };

type Method =
  | 'get'
  | 'head'
  | 'post'
  | 'put'
  | 'delete'
  | 'connect'
  | 'options'
  | 'trace'
  | 'patch';

type Handler = (req: Request, res: Response) => any;

export type Route = {
  method: Method;
  path: string;
  middleware: Middleware[];
  handler: Handler;
};
```

## Adding Some Structure

Instead of having all of our middleware and handlers in our `index.ts` file, let's add some structure.

### Handlers

First, let's move our user-related _handler_ functions into a `handlers` directory:

```bash
mkdir handlers
touch handlers/user.ts
```

Then, within our `handlers/user.ts` file, we can add the follow code. This represents the one user-related route handler (signing up) that we already have in our `index.ts` file, we're just reorganizing. Importantly, we can be sure that the `signup` function meets our need because it matches the type signature of the `Handler` type.

```typescript
import { addUser } from '../data/users';
import { Handler } from '../types';

export const signup: Handler = (req, res) => {
  const { username, password } = req.body;
  if (!username?.trim() || !password?.trim()) {
    return res.status(400).send('Bad username or password');
  }
  addUser({ username, password });
  res.status(201).send('User created');
};
```

Next up, let's add an auth handler that contains our `login` function.

```bash
touch handlers/auth.ts
```

Here's the code we can move to the `auth.ts` file:

```typescript
import { getUser } from '../data/users';
import { Handler } from '../types';

export const login: Handler = (req, res) => {
  const { username, password } = req.body;
  const found = getUser({ username, password });
  if (!found) {
    return res.status(401).send('Login failed');
  }
  res.status(200).send('Success');
};
```

Finally, we'll add one more handler for our home route ("Hello world").

```bash
touch handlers/home.ts
```

And this one is pretty simple:

```typescript
import { Handler } from '../types';

export const home: Handler = (req, res) => {
  res.send('Hello world');
};
```

### Middleware

We don't have any custom middleware yet, but let's change that! First, add a directory for our middleware:

```bash
mkdir middleware
```

We can add a middleware that will log the `path` that the client hit. We can call this `requestLogger.ts`:

```bash
touch middleware/requestLogger.ts
```

And in this file, we can once again import `RequestHandler` from express to make sure our middleware function is the right type:

```typescript
import { RequestHandler as Middleware } from 'express';

export const requestLogger: Middleware = (req, res, next) => {
  console.log(req.path);
  next();
};
```

### Creating Routes

Now that we have our fancy new `Route` type and our `handlers` and `middleware` organized into their own spaces, let's write some routes! We'll create a `routes.ts` file at our root directory.

```bash
touch routes.ts
```

And here's an example of what this file could look like. Note that I added our `requestLogger` middleware to just one of the routes to demonstrate how it might look&mdash;it otherwise doesn't make a whole lot of sense to log the request path for only one route!

```typescript
import { login } from './handlers/auth';
import { home } from './handlers/home';
import { signup } from './handlers/user';
import { requestLogger } from './middleware/requestLogger';
import { Route } from './types';

export const routes: Route[] = [
  {
    method: 'get',
    path: '/',
    middleware: [],
    handler: home,
  },
  {
    method: 'post',
    path: '/users',
    middleware: [],
    handler: signup,
  },
  {
    method: 'post',
    path: '/login',
    middleware: [requestLogger],
    handler: login,
  },
];
```

### Revamping Our index.ts File

Now the payoff! We can greatly simplify our `index.ts` file. We replace all our route code with a simple `forEach` loop that uses everything we specified in `routes.ts` to register our routes with express. Importantly, the Typescript compiler is happy because our `Route` type fits the shape of the corresponding express types.

```typescript
import express from 'express';
import bodyParser from 'body-parser';
import { routes } from './routes';

const app = express();
const PORT = 3000;

app.use(bodyParser.urlencoded({ extended: false }));

routes.forEach((route) => {
  const { method, path, middleware, handler } = route;
  app[method](path, ...middleware, handler);
});

app.listen(PORT, () => {
  console.log(`Express with Typescript! http://localhost:${PORT}`);
});
```

Wow this looks great! And, importantly, we have established a type-safe pattern by which we specify routes, middleware, and handlers.

# The App Code

If you'd like to see the final app code, head on over to the [github repository here](https://github.com/nas5w/express-typescript-app).

# Conclusion

Well, that was a fun exploration of express with Typescript! We see how, in its most basic form, it's not dissimlar to a typical express.js project. However, you can now use the awesome power of Typescript to give your project the structure you want in a very type-safe way.