Convert an Express NodeJS App From JavaScript to TypeScript
Tags: node, javascript, typescript, express
Published: 2020-07-18 15:42:21

---
title: Convert an Express NodeJS App From JavaScript to TypeScript
published: true
description: Convert an Express NodeJS App From JavaScript to TypeScript
tags: nodejs, javascript, typescript, expressjs
cover_image: https://tomalcala.com/uploads/node_ts.jpg
---

Hi! 🖖  

Today, I'll walk us through moving an Express NodeJS app from JavaScript
to TypeScript.  

__Why?__ TypeScript offers type safety "on demand", most of the code
won't break if you move your app from one to the other, and then, you
can add the safety where it is important.

## How

We are going to start from a [fork of Kent C. Dodds' Express example for
mid-large
apps](https://github.com/ThomasMarcel/express-app-example/tree/javascript).
I made a branch called `javascript` as a starter.  

### Nothing is lost, nothing is created, everything is transformed

Let's change the extension of all our app's js files to ts:

```
$ find . -type f -name '*.js' | grep -v node_modules | grep -v babelrc | while read line; do name=$(echo $line | sed 's/\.js$/.ts/'); mv $line $name; done
```

_We find all js files, ignore node_modules and babelrc, and rename them
to ts._  


### Adding TypeScript

1. Let's add the dependencies

```
$ yarn add typescript --dev
$ yarn add concurrently @types/express --dev
```

And in `package.json`, we add more scripts:

```
"scripts": {
    "start": "node .",
    "build": "babel --delete-dir-on-start --out-dir dist --copy-files --ignore \"**/__tests__/**,**/__mocks__/**\" --no-copy-ignored src",
    "start:dev": "nodemon dist/index.js",
    "build:dev": "tsc --watch --preserveWatchOutput",
    "dev": "concurrently \"npm:build:dev\" \"npm:start:dev\""
  },
```

2. Init the config

```
$ yarn tsc --init
```

_You can copy [my `tsconfig.json`](https://raw.githubusercontent.com/ThomasMarcel/express-app-example/main/tsconfig.json), I mainly added an output dire and small things like that._

3. Run the TypeScript compiler, crash and burn

```
$ yarn tsc
```

So, this breaks. Now let's fix the issues

### Fixing a File

Let's start with a small file: `src/index.ts`. It returns an error that
seems straightforward, but is representative of how TypeScript can be
annoying with little things.  

Here is the content of the file:

```
import logger from 'loglevel'
import {startServer} from './start'

const isTest = process.env.NODE_ENV === 'test'
const logLevel = process.env.LOG_LEVEL || (isTest ? 'warn' : 'info')

logger.setLevel(logLevel)

startServer()
```

And the error:

`src/index.ts:7:17 - error TS2345: Argument of type 'string' is not
assignable to parameter of type 'LogLevelDesc'.`

So here, we can see that `logger.setLevel()` is used to set the log
level, taking a `logLevel` variable. And it is going to be a string from
the `LOG_LEVEL` environment variable if defined, else based on the
`NODE_ENV` variable, it will be a string: 'warn' or 'info'.

__HOWEVER__, this crashes now, because in TypeScript, `setLevel()` takes
a `LogLevelDesc` type, which is essentially an integer with a fancy type
name.

Common libraries have types well documented, toplevel not really. So
I had to look at examples in the `node_modules`:

```
$ grep -rHin setlevel node_modules | less

node_modules/loglevel/test/node-integration.js:11:
log.setLevel(log.levels.TRACE);
node_modules/loglevel/test/node-integration.js:12:
log.setLevel(log.levels.DEBUG);
node_modules/loglevel/test/node-integration.js:13:
log.setLevel(log.levels.INFO);
node_modules/loglevel/test/node-integration.js:14:
log.setLevel(log.levels.WARN);
node_modules/loglevel/test/node-integration.js:15:
log.setLevel(log.levels.ERROR);
```

... So here we have some usage, for us it is going to be
`logger.levels.INFO`, etc, so we replace `"warn"` and `"info"` in `const
logLevel = process.env.LOG_LEVEL || (isTest ? 'warn' : 'info')` by
`logger.levels.WARN` and `logger.levels.INFO`

It's still not enough, because `process.env.LOG_LEVEL` is still
potentially there, and it's going to be a string. So I had to write
a function to convert the string and cast it in a `LogLevelDesc`:

```
const convertLogLevel: (logLevel: string | undefined) => logger.LogLevelDesc = (
  logLevel: string | undefined,
) => {
  switch (logLevel) {
    case "1":
    case "error":
      return logger.levels.ERROR;
    case "2":
    case "warn":
      return logger.levels.WARN;
    default:
      return logger.levels.INFO;
  }
};

const isTest = process.env.NODE_ENV === "test";
const logLevel: logger.LogLevelDesc = convertLogLevel(process.env.LOG_LEVEL) ||
  (isTest ? logger.levels.WARN : logger.levels.INFO);
```

As you can see in the first line, I had to specifically write the type
of the function `(logLevel: string | undefined) => logger.LogLevelDesc`
(a function signature is `(param1: type, param2: type, ...) =>
returnType`).

__I strongly recommend that you use a linter for your editor, so you can
see type errors while writing the code.__

Now that this file is fixed, let's try another one with Express code so
we see how this works for bigger, better documented libraries,

### Fixing an Express Route File

Now let's fix `src/routes/math.ts`. There is a problem with implicit
`any` type for req, res, etc. This can be solved by defining an explicit
type `any for those`:

```
async function add(req: any, res: any) {}
```

Types for request and stuff aren't safe and more of adding another
headache than a solution. I prefer creating a type for the query
parameters, this is more useful.

```
type MathQuery = {
  a: number;
  b: number;
  c: number;
};

async function add(req: any, res: any) {
  const mathQuery = req.query as MathQuery;
  const sum = Number(mathQuery.a) + Number(mathQuery.c);
  res.send(sum.toString());
}
```

So here, we cast `req.query` as MathQuery.

### Some Battles You Can't Win

We've seen well done transition to TypeScript, this latest compromise,
now we'll see a case where it is too painful to fix the code so we
ignore it.

I am a partisan of using TypeScript when it is useful, and try to use
the type system the most possible, to avoid errors at runtime.

That said, there are times when it is just too exhausting, painful and
a waste of time to use.  Here for example, the `src/start.ts` file is
a good example. Kent has wrapped the `startServer` and middleware
functions in promises with no type, no real return, just a resolution.
And I'm sure he knows what he's doing a lot better than me.

There is no way to match this signature without overriding or modifying
the node type definitions, so in that case when we know it's working,
it's faster and probably best just to ignore the type verification.

Simply add `// @ts-nocheck` at the top of the file.

## We've done it again! 🎉

[The final code](https://github.com/ThomasMarcel/express-app-example)