One of the key choices you make when building an enterprise Node.js application is whether to use plain JavaScript or a dialect that supports type validation. While participating in the Node.js reference architecture effort, we've pulled together many internal Red Hat and IBM teams to discuss our experience with using both plain JavaScript and TypeScript. Our projects seem to be split between the two, and it's often "love it or hate it" when using types with JavaScript.
TypeScript is in widespread use, particularly among enterprise developers coming from other languages such as Java. TypeScript was recently voted the third most loved programming language in StackOverflow's annual developer survey—far ahead of JavaScript itself.
This article covers why you might want to use TypeScript and how to get started, along with an introduction to the recommendations in the Node.js reference architecture. As with all our Node.js reference architecture recommendations, we focus on defining a set of good and reliable default choices. Some teams will deviate from the recommendations based on their assessment of what best fits their use case.
Follow the series:
- Part 1: Overview of the Node.js reference architecture
- Part 2: Logging in Node.js
- Part 3: Code consistency in Node.js
- Part 4: GraphQL in Node.js
- Part 5: Building good containers
- Part 6: Choosing web frameworks
- Part 7: Code Coverage
- Part 8: TypeScript
- Part 9: Securing Node.js applications
- Part 10: Accessibility
- Part 11: Typical development workflows
- Part 12: Npm development
- Part 13: Problem determination
- Part 14: Testing
- Part 15: Transaction handling
- Part 16: Load balancing, threading, and scaling
- Part 17: CI/CD best practices in Node.js
- Part 18: Wrapping up
Why use TypeScript?
JavaScript has come a long way from its humble beginnings as a lightweight scripting language inside the browser. Technologies such as Node.js have propelled it to become one of the leading languages for back-end development.
But as codebases grow in size, it can be increasingly difficult to track down errors and keep track of the data flowing through an application. That's true in any language, but it's a particular problem in weakly typed languages like JavaScript.
TypeScript is designed to address this problem. By adding type annotations to variables, TypeScript can help to document the data a program uses, catch errors, and give developers confidence that they can change code in one place without breaking other parts of their codebase.
Many code editors now have excellent TypeScript support. This support enables code completion, immediate feedback on type errors, powerful automatic refactoring, and other useful features. For instance, Visual Studio Code is a widely used editor that comes with extensive support for TypeScript. The TypeScript wiki contains a list of other editors with TypeScript support.
Most popular third-party JavaScript libraries now ship with TypeScript type definitions or make them available via the Definitely Typed repository.
These capabilities have caused TypeScript to explode in popularity.
Get started with TypeScript
TypeScript has been designed to be easy to adopt, even for existing JavaScript projects. You can incrementally enable TypeScript a single file at a time while leaving the rest of your project in JavaScript.
To demonstrate this flexibility, we'll port a very simple Node.js application to TypeScript. The application consists of a single JavaScript file named fill.js
in the project's src
directory. The code fills an array with a value:
function fillArray(len, val) {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(val);
}
return arr;
}
module.exports = { fillArray };
Step one is to install a TypeScript compiler. Because Node.js does not natively understand TypeScript files, they must be compiled to JavaScript before they can be executed. The compilation from TypeScript to JavaScript is called transpiling. There are multiple transpilers available (see the reference architecture for details), but we'll use the standard TypeScript compiler tsc
. Install it as follows:
npm install --save-dev typescript
If you're using any built-in Node.js modules, you also need the types for these:
npm install --save-dev @types/node
The compilation process is configured using a tsconfig.json
file. This configuration controls all the parameters for TypeScript compilation. The Node.js community maintains a recommended configuration that you can install as follows:
npm install --save-dev @tsconfig/node16
If you are using a Node.js version older than 16, you can check the list of bases for recommended configurations compatible with older versions.
Add Node.js options to your tsconfig.json
file as follows:
{
"extends": "@tsconfig/node16/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"strict": false,
"outDir": "./build"
},
"include": ["./src/**/*"]
}
This configuration specifies that all files under the src
directory should be compiled and put in the build directory. It also allows your source files to stay written in JavaScript (these will be copied across to the build directory without modification) and disables strict mode (further details later on strict mode). There are many further options that you can set—please see our recommendations in the reference architecture.
To run the compile, execute:
npx tsc
In this simple example, because we haven't defined any data types, the compiler created an identical fill.js
file in the build directory.
Adding some TypeScript
Node.js supports two module systems:
- CommonJS: The traditional format, which uses the
require
keyword to import code andmodule.exports
to export it. - ES modules: A newer format using the
import
keyword to import code and theexport
keyword to export it. This format is supported by both Node.js and web browsers.
TypeScript supports only the ES module format, so in addition to renaming your example file to src/fill.ts
, you need to update its export:
export function fillArray(len, val) {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(val);
}
return arr;
}
This code now compiles successfully, even though you haven't added any types. This is because strict mode is set to false in the tsconfig.json
file. If you set the mode to true
, you will see an error like the following when you compile:
src/fill.ts:1:27 - error TS7006: Parameter 'len' implicitly has an 'any' type. src/fill.ts:1:32 - error TS7006: Parameter 'val' implicitly has an 'any' type.
You can add some annotations to the argument list in the first line to fix these errors:
export function fillArray(len: number, val: any) {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(val);
}
return arr;
}
The changes make the compilation succeed. Even better, if you accidentally forget which way round the parameters go and call the method like this:
console.log(fillArray("-", 5));
TypeScript gives another helpful error:
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
We recommend enabling strict mode for new projects, but when migrating existing projects it may be easier to leave the mode disabled.
Many editors can be configured to immediately show TypeScript errors rather than waiting until you run the compiler. Editors may also offer other advanced features such as code completion and automatic refactoring.
Node.js reference architecture recommendations
Teams need to make a number of key choices when using TypeScript. These include:
- Should transpilers be used? If so, which ones?
- What should be shipped: the original files, or the transpiled versions?
- What TypeScript options and configuration should be used?
- How should types for npm packages be published?
The Node.js reference architecture contains further recommendations, including how to use TypeScript with tools such as nodemon
and best practices for deployment based on the experience our team has gained through deployments within Red Hat, IBM, and our customers.
Those recommendations are well defined in the Node.js reference architecture, so instead of repeating them here, we encourage you to head over to the TypeScript section of the reference architecture itself.
What's next?
We cover new topics regularly as part of the Node.js reference architecture series. The next article in this series focuses on key security elements that JavaScript developers should address.
We invite you to visit the Node.js reference architecture repository on GitHub, where you'll see the work we've already done and the kinds of topics you can look forward to in the future.
To learn more about what Red Hat is up to on the Node.js front, check out our Node.js landing page.
Last updated: February 11, 2024