TypeScript adds static typing to JavaScript code, which helps reduce unpredictable behavior and bugs. In the past, TypeScript code couldn’t run directly in Node.js. The default way to run TypeScript in the runtime was to first compile it to JavaScript using the TypeScript CLI tool (tsc
) and then run the resulting code.
If you wanted to run TypeScript directly in Node.js, you had to rely on third-party tools like ts-node and tsx. However, with the v22.6 release of Node.js, the runtime added experimental lightweight support for TypeScript. Node.js implemented TypeScript support using a method known as type stripping, effectively turning Node.js into a TypeScript runner.
But why do we need TypeScript runners at all? Why wasn’t the original TypeScript compilation process enough? In this article, we’ll explore the benefits of running TypeScript directly in Node.js and compare different ways to do so.
The official TypesScript library does a lot of important work in a project. It checks the code for syntax errors, validates and infers types, parses the tsconfig.json
file for custom instructions, and transpiles TypeScript code to JavaScript — which a JavaScript runtime can now execute.
Technically, the TypeScript compiler should have been enough for developing TypeScript apps, as the most popular JavaScript runtimes — a browser or Node.js — did not understand TypeScript code anyway. But in practice, a TypeScript runner can be more efficient, especially during development, which is a feature neither Node.js nor the TypeScript library supported had in the past.
A TypeScript runner is any program that directly executes TypeScript code. Other JavaScript runtimes like Bun and Deno have TypeScript runners built in. The runner allows developers to execute TypeScript code in one step instead of the default two steps of transpiling and then executing the resulting JavaScript code.
TypeScript runners handle two development steps at once. Also, using them is usually faster than the default compilation process (during fast-paced development). This is because the runners are typically heavily optimized, which is especially helpful in larger projects. TypeScript runners are also less complicated to use when you just want to test or execute a script.
Some runners come with extra features like a watch mode and a REPL, and some of them can even perform type checking.
The rest of this article will look into three ways of running TypeScript in Node.js:
We’ll list the features of each tool and discuss the trade-offs developers make when choosing between them.
In v22.6, Node.js released the --experimental-strip-types
CLI flag. When used, this flag transforms a TypeScript file to JavaScript (using type stripping) before executing it. Type stripping is a process where all the type declarations and annotation syntax in TypeScript code are removed (stripped). If TypeScript code contains only erasable syntax, type stripping automatically turns it into JavaScript code.
“Erasable syntax” in this context refers to TypeScript-specific syntax that doesn’t contain any values needed at runtime. Node.js handles type stripping using the npm module amaro. amaro is a lightweight and optimized tool that replaces erasable code in TypeScript with white space (similar to ts-blank-space). Replacing the erasable code with white space means there is no definite need to generate source maps since errors are detected at their original line of code.
The type stripping process in Node.js gives it very minimal support for TypeScript. Since its v23.6 release, Node.js now enables --experimental-strip-types
by default. This means you could run TypeScript files in the runtime without using any flag (as long as they have the same version or higher).
In the v22.7 release, Node.js added a new CLI flag: --experimental-transform-types
. When used, this flag enables type stripping, as well as transforms TypeScript code with non-erasable syntax to JavaScript. However, it needs to generate source maps and thus is not a very lightweight approach (compared to just type stripping).
Node.js can only remove erasable code — it cannot type check the code. Instead, it leaves that to the developer. To ensure fast-paced development, the IDE is usually sufficient for flagging syntax or type errors. You can implement type checking in the linting process before committing to the version control system or deploying to production.
Here are some downsides to consider before using this new Node.js feature:
--experimental-transform-types
flagtsconfig.json
file and does not use any custom instructions. This implies that the internal transpilation process is not configurable in any wayWhile the Node.js runtime doesn’t need a tsconfig.json
file, nor does it type check the code, Node.js still highly recommends type checking your TypeScript code before deployment. In order to complement this new type stripping feature, TypeScript released the config option and CLI flag named erasableSyntaxOnly
:
// tsconfig.json { "compilerOptions": { // ... erasableSyntaxOnly: true, } }
When enabled, this option (set to true
in the TypeScript configuration file or called in the CLI as --erasableSyntaxOnly
), makes sure the TypeScript compiler returns an error message when the source code contains syntax that cannot be type stripped. When a developer enables this option, they can be sure that whatever code the tsc
tool compiles successfully can also run on Node.js.
This new option is very important, especially for code editors that rely on the tsconfig.json
to know when to show errors and warnings.
This section offers a step-by-step guide of how to use Node.js type stripping in a project. But before these steps, make sure to update your code editor to its latest version for better editor type checking and support. Also, ensure the Node.js version is v22.6 or greater. Here are the steps:
Create a tsconfig.json
file. Even though Node.js does not work with the file, make sure to use the following minimum configuration. This closely mirrors how Node.js handles TypeScript files and ensures consistency regardless of the tools one uses to run the TypeScript code:
// tsconfig.json { // ... "compilerOptions": { "noEmit": true, "target": "esnext", "module": "nodenext", "rewriteRelativeImportExtensions": true, "erasableSyntaxOnly": true, "verbatimModuleSyntax": true } }
Make sure to install Node.js types for type safety when working with Node.js modules and APIs:
npm install --save-dev @types/node
Next, write TypeScript code in a new file:
// index.ts function add(a: number, b: number) { return a + b; } console.log(add(2, 2));
Run the code on the command line:
node index.ts # If using Node.js v23.6 or more node --experimental-strip-types index.ts # If using Node.js v22.6 or more
This produces the outcome of 4
.
You can also combine running TypeScript code during development with type stripping with a script that compiles the code when needed. Perhaps when pushing code updates to the version control system or when deploying code. Using the above index.ts
file, add type checking by first installing the official TypeScript library:
npm install --save-dev typescript
Then configure the package.json
to run using Node.js watch mode for development and an npm script to type check with TypeScript:
// package.json { // ... "scripts": { "dev": "node --watch index.ts", "typecheck": "tsc" }, }
The best use case for Node.js type stripping is fast code execution during development. However, since the feature is still technically unstable at the time of writing, it’s not recommended for production use. Additionally, you can’t publish npm packages with TypeScript. So, after running type checks with tsc
, you’ll need to use a transpiler tool like swc or esbuild to instantly convert the code to JavaScript for execution.
ts-node is a third-party npm module used to run TypeScript code directly in Node.js. It is also a TypeScript REPL library for Node.js. ts-node has full TypeScript code, unlike Node.js type stripping, where full support has to be enabled. ts-node also type checks TypeScript code by default unless a developer opts out with a --transpileOnly
flag. The compilation process is also customizable with the tsconfig.json
file when using ts-node.
In addition to all of this, ts-node has a plugin ecosystem and supports third-party transpilers (like one that uses swc
). ts-node is suitable for precompiling code before it is deployed to production, but it can also run TypeScript code in production if a developer wishes to use it.
In order to use ts-node in a Node.js project, follow these steps:
First, install ts-node as a dev dependency. Also, install typescript
as it is a peer dependency to ts-node:
npm install -D ts-node typescript
Then, assuming a TypeScript file index.ts
exists like in the previous section, execute it in Node.js as so
npx ts-node index.js # If module type is common js npx node --loader ts-node/esm index.ts # If using ESM module type
And that is all you need to run a file with ts-node
!
You can also set up a package.json
to have scripts that handle type checking and transpilation separately:
{ // .. "type": "module", "scripts": { "dev": "node --watch --loader ts-node/esm index.ts", "typecheck": "tsc", }, }
In the tsconfig.json
, set up configurations for ts-node with options in the "ts-node"
property:
{ // ... "ts-node": { "transpileOnly": true } }
ts-node has a few configuration options and CLI flags, all detailed in its documentation.
With the package.json
file above, running npm run dev
during development executes the TypeScript code.
ts-node can type check as well as execute TypeScript code. There is usually little need to type check TypeScript code in the development phase because the code editors will highlight type errors. However, to ensure quality before deployment, it is important to type check the TypeScript code.
After type checking, one can use ts-node to run TypeScript code directly in production. But that adds a risky and avoidable overhead to just running JavaScript code directly. For example, when working with ES modules, you can’t use ts-node in production because it relies on loaders to transpile the TypeScript code (loaders are an experimental feature of Node.js)
However, for CommonJS apps, make sure to enable the swc
plugin, and enable the transpileOnly
option. Finally, when type checking with tsc
, make sure to use the --noEmit
flag or set its equivalent in the tsconfig.json
file.
ts-node may offer many features, but there are a few downsides to using it. First, the code base is not well-maintained. At the time of writing, there have been no new minor or major releases for two years, even though its repository shows some highlighted “issues” that need to be attended to.
Second, ts-node is very advanced, therefore, it can be a lot more complicated to use for a beginner compared to an alternative like tsx
or Node.js type stripping.
Use ts-node for a faster development pace in transpile-only mode. That is the best use case for the package.
The npm package tsx (not to be confused with .tsx
, the file extension for JSX written withTypeScript) calls itself a “Node.js enhancement.” It is a third-party package that uses Node.js to execute TypeScript code just like ts-node. tsx tries to be intuitive and beginner-friendly. When installed, tsx is a node
alias; this means it accepts all Node.js CLI flags and arguments. The difference is that tsx is also capable of running TypeScript files.
Without a tsconfig.json
file in a project, tsx runs the provided TypeScript code with “modern and sensible defaults”. This means using a tsconfig.json
file with tsx is optional.
tsx comes with a robust watch mode feature. Like Node.js type stripping, tsx doesn’t support type checking. However, it comes with a TypeScript REPL like ts-node. In addition to all of this, because tsx uses esbuild
under the hood and is heavily optimized itself, it is guaranteed to transpile and execute TypeScript very quickly.
To use tsx, first install it as a dev dependency to a project:
npm install -D tsx
A tsconfig.json
file might not be compulsory with tsx, but the library recommends it. Create one with the following recommended configuration:
// tsconfig.json { // ... "compilerOptions": { "moduleDetection": "force", "module": "Preserve", "resolveJsonModule": true, "allowJs": true, "esModuleInterop": true, "isolatedModules": true, } }
After that, use tsx to execute a file like you would with the node
CLI command:
npx tsx index.js
Or set up scripts in the package.json
file:
// package.json { // ... "scripts": { "dev": "tsx index.ts" } }
tsx utilizes esbuild
for transpilation and is potentially a good choice for production. However, a developer choosing this route would still have to handle type checking separately. But, just like ts-node, you would get the best use out of tsx in production instead of introducing a production overhead.
tsx is a regularly updated library with fairly detailed documentation. A downside to using it is its installation bundle size, which was 23 MB at the time of writing (according to https://pkg-size.dev/tsx). But that is not a big reason not to use the library, just something to note.
Features | Node.js type stripping | ts-node | tsx |
---|---|---|---|
GitHub stars (at the time of writing) | N/A | 13K Stars | 10K Stars |
Bundle install size | N/A | 27MB | 23MB |
Requires CommonJS modules without .ts file extension |
❌ | ✅ | ✅ |
Imports ES modules without .ts file extension | ❌ | ❌ | ✅ |
Uses tsconfig paths | ❌ | ❌ | ✅ |
TypeScript REPL | ❌ | ✅ | ✅ |
Built-in watch mode | ✅ (Node.js watch mode) | ❌ | ✅ |
Type checking | ❌ | ✅ | ❌ |
Parses tsconfig.json |
❌ | ✅ | ✅ |
Full TypeScript support | Experimental with --experimental-transform-types |
✅ | ✅ |
The aim of this article was to show different ways of running Typescript directly in Node.js. The article started by stating why the TypeScript compiler might not be enough for developing Typescript Apps. The article introduced the Node.js Type Stripping feature, how to use it, and some drawbacks it has.
The article then discussed ts-node and tsx, which are two third-party npm libraries that provide a TypeScript REPL and are used to execute TypeScript code in Node.js. The article showed how to use them and the possible downsides they have.
The addition of type stripping to Node.js guarantees that for many use cases, a developer may not need to install third-party Typescript runners anymore. With the experimental-transform-types
feature in development, it places Node.js on a path to fully run TypeScript applications like Deno or Bun sometime in the future.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore the new React ViewTransition, addTransitionType, and Activity APIs by building an AirBnB clone project.
Compare gRPC vs REST to understand differences in performance, efficiency, and architecture for building modern APIs.
The switch to Go may be a pragmatic move in the short term, but it risks alienating the very developers who built the tools that made TypeScript indispensable in the first place.
Discover the basics and advanced use cases of type casting, how and why to use it to fix type mismatches, and gain some clarity on casting vs. assertion.