Babel with TypeScript on Node.js from scratch - JSCasts Episode 13

In this episode I will show you how you can add Babel to your Node.js project.With Babel you can use the latest javascript features even on an older Node.js version.

It can be also used to change fancy frontend files like *.vue or *.jsx to JavaScript - operation required to perform Server Side Rendering.

I will cover Server Side Rendering in the next episode but now - let’s add TypeScript support to the example express server. This will give you a better understanding of how babel works and how you can incorporate it in your development pipeline.

You can also watch the full tutorial in this video:

Let's start!

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

This server is a copy of the example application from express page and we will babelize it.

Create Babel Configuration File

First - let’s create a babel configuration file.
Babel uses 2 types of configuration files:

  • project-wide configuration files. It means one config for the  entire project, or
  • file-relative configuration files. This works in a similar way to .gitignore - it controls the folder in which it is placed.

We will use the second one, so let’s create .babelrc file in our example application.

{
  "presets": ["@babel/preset-env"]
}

Babel works based on plugins. You have plugins which convert arrow functions, spread syntax etc. to the given environment. But to simplify things babel gathers related plugins into  presets. The most popular preset is an “env” preset. It contains all the features from the latest ECMAScript standard.

Transpile code to Node version 4

Along with the preset, you can pass some options. So let’s define that babel should transpile code to node version 4.

In order to do that we have to wrap the preset into an array and as a second element pass the JSON object with `targets` property

{
  "presets": [["@babel/preset-env", {
    targets: {“node”: 4}
  }]]
}

Now, let’s add 3 dev dependencies to our project @babel/core, @babel/register and @babel/preset-env

yarn add --dev @babel/core @babel/register @babel/preset-env

We are almost done - now let’s require @babel/register

require("@babel/register")();

On top of our entry index.js file.

This package causes that all consecutive require statements will be transpiled by babel.

Finally, let’s move the code to a ./src directory to a new file: app.js

Change require statements to import statements

Let’s see if it works. Perfect!

Adding TypeScript support

Now we can add typescript support. We have to install another preset

yarn add --dev @babel/preset-typescript

and update .babelrc.

{
  "presets": [["@babel/preset-env", {
    targets: {“node”: 4}
  }], '@babel/preset-typescript']
}

By default, babel doesn’t parse files with extensions .ts so we have to change our babel/register call. It is worth mention that we can not specify extensions in .babelrc.

require("@babel/register")({extensions: ['.js', '.ts']})

Let’s rename our file to app.ts

Now we can create a typescript class to see if it works. Create a new file. Name it app-controller.ts It will be super simple - just a constructor with one method.

export default class AppController {
  constructor(private request, private response) {}

  async index (): Promise<string> {
    return 'Hello world'
  }
}

Update the original express server to use this controller. Ok - now rerun the app.

And there is an error.

It is related to the async await statements which we used in the class. To handle them we have to add yet another plugin called “transform runtime”.

It has 2 dependencies. One, the plugin itself - which should be installed as a dev dependency:

yarn add --dev @babel/plugin-transform-runtime

Next, there is @babel/runtime - installed as a regular dependency:

yarn add @babel/runtime

And we have to update .babelrc with plugins section.

{
  "presets": [["@babel/preset-env", {
    "targets": {
      "node": 4
    }
  }], "@babel/preset-typescript"],
  "plugins": ["@babel/plugin-transform-runtime"]
}

Now we can rerun the app. Everything seems to be working, but check it out on browser site. Ok - we see a slightly different message.

So it works!

Running app on a production

Now let’s discuss how to run the application in production. Obviously we cannot use babel register because our app will be slower and will occupy more memory. So let’s enable it only if certain env variable is set. Otherwise lets import files from the ./lib folder instead of ./src.

Now let’s create the ./lib folder.

We have to add a couple of scripts to the package.json file:

First, let’s add ‘dev’ command which will run the server in the dev environment. We use the env variable we defined before. I change node to nodemon - this will cause automatic reload of the app when code is updated.

"dev": "DEV_ENV=true nodemon index.js",

Next comes regular start script.

"start": "node index.js",

And finally, the build command which will convert all the files placed in ./src and put them into the ./lib directory. We specify that we copy all the files (even if they are not javascript/typescript file, which will be handy when we will add some static assets) and we transpile extensions for both typescript and javascript. - the same as we did in babel register.

"build": "babel src --out-dir lib --copy-files --extensions '.ts,.js'"

We have to add another dependency @babel/cli

yarn add @babel/cli

OK - Lets test the dev environment first:

yarn dev

it works - so let’s test production:

yarn start

It doesn’t work - that was expected - so let’s build it first:

Yarn build

And rerun it. Ok now it works.

Let’s see our transpiled files. Crazy - but it works with Node version 4.

Usually, you might want to add ./lib folder to .gitignore and build the app in the continuous integration pipeline.

Types checking

There is one crucial thing regarding typescript with babel - namely, babel only transpiles the files - it does not check types.

For types checking you have to use typescript tsc command with .tsconfig.json separately.

So let’s add an example typescript config file.

{
  "compilerOptions": {
    "esModuleInterop": true,
    "allowJs": true,
    "noEmit": true,
    "moduleResolution": "node",
    "strict": true
  },
  "include": [
    "./src/**/*"
  ]
}

Where we define noEmit to true which prevents typescript from generating any files - just the type checking. You can check out the other options in typescript documentation. Finally, let’s add “types” command

"types": "tsc",

And add typescript to our project

yarn add --dev typescript

Run types command.
It works - now we might want to fix all the errors, but I won’t bore you with that.

Last words

Ok - this is everything for now. I’ve just shown you how you can add babel to your Node.js project. I hope you liked this episode - if you did, subscribe the channel and see you next week!