Typescript with NodeJS: An Integration Guide

Introducing Typescript and how to integrate it into your NodeJS projects

Ross Bulat
Feb 7 · 10 min read

The Typed Javascript Superset by Microsoft

As of December 2018 around 47% of Javascript developers are now adopting Typescript in their Javascript stack. This article will introduce Typescript, go through the process of installing it in a NodeJS environment, and visit the conventions required to support Typescript in your projects. By the end of this article you will have a clear answer to whether it is worth adopting Typescript in your Node projects.

But, What exactly is Typescript?

Typescript, now on version 3, allows us to type and interface JavaScript code. It also includes a range of features that can improve syntax, such as iterators, decorators and namespacing. These features and more aim to improve the scalability and readability of your apps. Typescript essentially gives us a typing system on top of Javascript, rather than providing an entirely new language.

Many developers report that simply introducing Typescript to their existing projects uncover undiscovered bugs and generally makes their projects more readable. I can attest to this; on-boarding developers to your Javascript projects will be smoother with Typescript; data structures and data flow will become more apparent, and code indeed becomes more readable.

On the surface there is good reason to adopt Typescript, and even more so upon inspecting community adoption. Typescript is a hot package. With weekly NPM downloads above 4 million at the time of this writing, it is a fair assumption that the Javascript community have accepted Typescript as a valuable addition to their Javascript stacks, and that it is here to stay. The package’s popularity has also accumulated over 3,000 open issues on Github; the repository is regularly updated, and also stays up to date with Javascript language specifications.

Typescript is also being used in conjunction with Javascript frameworks including React and Angular; although this article is focused on NodeJS it is worth noting that developers are rapidly adopting TypesScript for their front-ends too.

If Typescript sounds good to you so far, let’s move on to how it is integrated with Node.

How is Typescript Integrated?

Typescript is installed as a global dependency and is only needed in your development environments. Included in the typescript package is a Typescript compiler that can be invoked with the tsc command. Running the compiler will take all your .ts files within your project src/ directory, and compile them into vanilla .js files in an output folder of your choosing.

This output folder is conventionally called the dist/ folder, whereas our .ts files conventionally reside in the src/ folder. This process does involve some configuration, all of which we will cover.

IDE Support

Typescript has great support for a range of IDEs; this Github page provides a comprehensive list of supported editors and links to the tools. Unsurprisingly, the Visual Studio suite contains some of the best integration with Typescript. If you are a VS user Typescript will be installed as part of Microsoft Web Tools, thus no further setup is needed.

I have personally opted to use Sublime Text 3. The Typescript Sublime Plugin uses an IO wrapper around the Typescript language services, with auto-complete and syntax highlighting supported. It is available via Package Control; search for TypeScript package to install.

Alternatively, if you are like me, someone who spends a lot of time in China where Package Control regularly times out, you can manually install the package with the following commands:

cd ~/"Library/Application Support/Sublime Text 3/Packages"
git clone --depth 1 https://github.com/Microsoft/TypeScript-Sublime-Plugin.git TypeScript

Windows users also have installation instructions; see them here. Once your tools are installed we are ready to delve into setting up Typescript within your Node projects.

The SublimeLinter-tslint package is also available for Typescript linting in the Sublime Text editor.

Installing TypeScript with Node

Introducing Typescript will involve some infrastructure changes throughout your folder structure and will ultimately change some of your code logic. There will most likely also be initial errors when introducing types to your objects; code breakage probability is high initially.

For this reason, let’s branch out from your master branch with a dedicated typescript branch:

git branch origin typescript
git checkout typescript

If you are starting a project from scratch there is no need to worry about this. Also, install typescript globally if you have not done so already:

npm install -g typescript

Amending package.json

We need some means of compiling Typescript in our project, and the package.json scripts block can play a role for just this. Include the following 2 commands that call the Typescript compiler, the second one launching the compiler in watch mode:

#package.json{
"scripts": {
...
"build-ts": "tsc",
"watch-ts": "tsc -w",
}
...
}

Now running yarn build-ts or yarn watch-ts will invoke the Typescript tsc compiler. Using the npm command will work as well as yarn — use which one you have adopted as your project’s package manager.

Introducing jsconfig.json

What we will want to do next is configure the Typescript compiler to behave the way we want. To do this, a tsconfig.json file is included in your project root directory. Let’s take a look at an example covering specific configuration for NodeJS:

# jsconfig.json{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": [
"node_modules/*",
"src/types/*"
]
}
},
"include": [
"src/**/*"
]
}

Copying this entire configuration file will work within your Node project for this talk. Let’s briefly break down what is being configured here:

  • "module": "commonjs": This allows us to define the output module type. commonjs is the standard module format used in Node.js, therefore this is what we will adhere to.
  • "esModuleInterop": true: Allows usage of an alternate module import syntax; you may be using syntax like import bar from 'foobar';, for example.
  • "target": "es6": The output Javascript specification to adhere to. NodeJS supports ES6, therefore we can output ES6 syntax.
  • "noImplicitAny": true: Setting this to true will throw an error when something has a default type of any. An object typed with any means that any value can be expected. Setting this to false may make more sense if you are in the process of converting Javascript to Typescript, as you work out which types to use throughout the project.
  • "moduleResolution": "node": Typescript attempts to mimic Node’s module resolution strategy.
  • "sourceMap": true: This allows source maps to be output alongside Javascript. What this allows us to do is halt execution at run time in the event we wish to debug Javascript being output, in the form of break points in the source map files.
  • "outDir": "dist": The location to output .js files to after compilation. This is our dist/ folder.
  • baseUrl and paths: These values allow us to map more paths for Typescript to scan upon compilation. From the base URL of the project root, we are mapping the node_modules/ and src/types/ folders as additional paths. Why would we do this? Because we may wish to search or add additional .d.ts (DefinitelyTyped) files into our project. We will visit this next.
  • "include": include takes an array of glob patterns of files to include in the compilation process; here we are including every file and folder from src/.

Introduce the src/ and dist/ folders to your project now. The dist/ folder is initially empty and will be populated upon the first successful compile of the src/ folder.

If you already have a src/ folder, it may be beneficial to introduce a ts-src/ folder instead, and amend tsconfig.json with this folder instead of src/. To save confusion I will continue to refer to this folder as the src/ folder.

Adding DefinitelyTyped files

Now, the final configuration to visit before the conversion process to .ts are .d.ts files. Typescript uses .d.ts files to provide types for JavaScript libraries that were not originally written in Typescript. The chances are that the majority of libraries you have installed do indeed have .d.ts files.

Adding .d.ts files for your dependencies will ensure Typescript catches any errors attributed to how you are using objects derived from modules; this is just as important as catching errors in your own project code. There are a few ways we can tackle type definition files. Let’s go through them.

Option 1: Adding readily available @types packages

Imagine you are using the mongodb module in your project, and you wish to install type definitions for this module so Typescript can check the library and provide more assistance in your editor. We can install these types just like any other package, and there is indeed a MongoDB types package available to install.

All .d.ts package names will follow the following naming convention:

@types/<name_of_module>

Therefore, simply add the @types/mongodb package with the following:

npm i --save @types/mongodb
#or
yarn add @types/mongodb --dev

Option 2: Using a generator such as DTS-Gen

In the case a @types package does not exist for a particular module, check out a tool named dts-gen — a Typescript Definition File Generator, by Microsoft. Providing a definition file for some_module is as simple as running the following:

#install dts-gen
npm install -g dts-gen
#install some_module
npm install -g some_module
#generate .d.ts file for some_module
dts-gen -m some_module

Remember the additional path /src/types we defined in the tsconfig.json? This is where we will generate these additional definition files.

Take note of the caveats in the README section, notably that there will most likely be lots of any types generated. But nonetheless, this tool is widely used for missing module definition files, and is better than a blank definition file!

Option 3: Defining blank definition files

If for any reason dts-gen fails or brings errors to the compilation process, it is entirely valid to provide a blank .d.ts file for a module. Again, within your /src/types folder, declare the definition file like so:

# src/types/some_module.d.tsdeclare module "some_module";

Once your modules have received the .d.ts treatment, there should be no compilation errors as a result of missing definition files.

Converting .js to .ts

Before continuing, install the .d.ts file for NodeJS:

npm install --save @types/node

All your .js files now need to be convered to .ts files. Now — this process may take a while depending on the size of your project; don’t expect this to be done in an afternoon. In addition, converting a sizeable project in its entirety in one iteration will greatly increase the probability of Typescript errors.

Suffice to say, copying the entire project into src/ and amending .js to .ts is not a favourable strategy.

What we can opt to do instead is move the project in chunks, verifying that the code works in stages and managing any errors that crop up along the way. In any case, let’s start introducing Typescript in a gradual manor to verify the compiler is working as expected.

TSC Compiler Test

Let’s firstly introduce a helloworld.ts file and run the Typescript compiler to verify everything is working as it should be. Our helloworld.ts script will contain our first typed Javascript variable — a string:

// src/helloworld.tsconst msg: string = "Hello World!";
console.log(msg);

Our first Typescript file is complete. We define a type using a colon after the variable name, followed by the type. Very standard syntax here. Now run the tsc compiler we set up earlier in package.json:

#compile Typescriptyarn build-ts

Doing so will yield a successful compile with a helloworld.js file sitting in our dist/ folder. Alternatively we could run yarn watch-ts within another Terminal window, which will automatically re-compile our project upon changes being made.

At this stage all that is needed is to either continue building your project via .ts files, or copy your existing .js files and convert them to .ts files.

Moving Express boilerplace to .ts

To make this processes clearer, let’s refer to an Express boilerplate setup. In this case, all boilerplate will need to be moved into the src/ folder:

/node_modules
package.json
tsconfig.json
/dist
/src
app.js
public/
routes/
views/
types/
...

The src/ folder is now populated with our Express boilerplate. package.json, node_modules/ and other configuration is left at your project root. The types/ folder is also added for hosting .d.ts files not available from DefinitelyTyped packages. Nothing new here.

Now we will need to start the conversion process, changing all the .js files within src/ to .ts files.

Note: Existing projects at this stage will not be affected by Typescript additions; all our Typescript code will be sitting in src/ away from your app logic. app.js currently has no idea that your Typescript src folder exists. What we will do now is direct bin/www to require an app.js file within dist/, rather than the existing app.js file.

Additional bin/www for Typescript compiled app.js

Before wrapping up this talk, it would be nice if we could run a separate app.js file within our dist/ to test our Typescript compiled version of our app, rather than the original vanilla Javascript app.js file. We can do this by either amending bin/www or duplicating it, for example, to bin/ts-www. Once your conversion to Typescript is complete, you could revert back to a standalone bin/www executable.

To swap app.js to our TypeScriped compiled app.js version, change:

var app = require('../app');

to:

var app = require('../dist/app');

Now calling your amended bin/www with nodemon or node will run our compiled Typescript app.

A note on testing with Jest

You will most likely be interested in a testing framework used in conjunction with Typescript. My testing framework of choice is Jest, which is fully Typescript supported. Install with the following:

npm install -D jest ts-jest

Now include a jest.config.js file within your project root:

#jest.config.js module.exports = {
globals: {
'ts-jest': {
tsConfigFile: 'tsconfig.json'
}
},
moduleFileExtensions: [
'ts',
'js'
],
transform: {
'^.+\\.(ts|tsx)$': './node_modules/ts-jest/preprocessor.js'
},
testMatch: [
'**/test/**/*.test.(ts|js)'
],
testEnvironment: 'node'
};

Here we are telling Jest to look for tests within a test/ folder within project root. Add that folder now for this config to be valid.

Now, upon running npm run test our tests will be precompiled into Javascript before the tests are executed.This all happens in memory upon running the test command.

What Next?

This article has covered the process of introducing Typescript into your Node projects, how to correctly configure the compiler, the process of adding .d.ts files for your existing modules, as well as the strategy to gradually, or iteratively, convert your project from .js to .ts.

This last task of converting .js to .ts does require knowledge of Typescript syntax — something we have not covered here. If you are not familiar with Typescript syntax, the Handbook section of the Typescript documentation outlines all the features of the language.

This article focused on transitioning Node projects from Javascript to Typescript. If you are looking to set up a Typescript based node project from scratch, I have documented the process and standard tools in a live chat Typescript by example series:

Ross Bulat

Written by

Author and programmer. Director @ JKRBInvestments.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade