Running TypeScript (ts-node, tsx, Bundlers)
By default, Node.js cannot execute .ts files directly — you first compile them to
JavaScript with tsc, then run the .js output. That two-step process is fine for
production builds, but tedious during development.
Several tools let you run TypeScript directly without a manual compile step. This page
covers every major approach: from the venerable ts-node to the modern tsx, from
bundlers like Vite and webpack to the native Node.js 22.6+ flag.
The Options at a Glance
Tool | Type-checks? | ESM support | Speed | Best for |
|---|---|---|---|---|
tsc + node | Yes | Yes | Slow (full compile) | Production builds |
ts-node | Yes (default) | Via --esm flag | Moderate | Scripts that need type safety |
ts-node --transpileOnly | No | Via --esm flag | Fast | Quick scripts |
tsx | No | Yes | Very fast (esbuild) | Development scripts, CLIs |
esbuild | No | Yes | Extremely fast | Bundling and transpilation |
Vite | No (but tsc --noEmit) | Yes | Very fast (esbuild/Rollup) | Frontend apps |
webpack + ts-loader | Yes | Yes | Moderate | Complex frontend builds |
Next.js | No (but tsc --noEmit) | Yes | Fast (SWC) | Full-stack React apps |
Bun | No | Yes | Extremely fast | Fast scripts and servers |
Node.js 22.6+ --experimental-transform-types | No | Yes | Fast (built-in) | Native Node experiments |
ts-node
ts-node is the original TypeScript execution engine for Node.js. It hooks into
Node's module loader and compiles .ts files on-the-fly using the TypeScript
compiler. By default it performs full type-checking, which makes it slower than
newer alternatives — but it is the safest option for scripts where you want
compile-time guarantees.
# Install npm install --save-dev ts-node typescript @types/node # Run a TypeScript file directly npx ts-node src/index.ts # Run with the REPL (interactive TypeScript shell) npx ts-node
$ npx ts-node src/greet.ts Hello from TypeScript!
ts-node respects your tsconfig.json. The most useful options:
# Skip type-checking (just transpile — much faster)
npx ts-node --transpile-only src/index.ts
# Use a specific tsconfig
npx ts-node --project tsconfig.scripts.json src/index.ts
# Pass TypeScript compiler options inline
npx ts-node --compiler-options '{"module":"commonjs"}' src/index.tsAdd ts-node to your package.json scripts:
{
"scripts": {
"start": "ts-node src/index.ts",
"start:fast": "ts-node --transpile-only src/index.ts",
"dev": "ts-node --watch src/index.ts"
}
}ts-node with ESM
By default ts-node targets CommonJS. To use ES Modules you need the --esm flag
and a matching tsconfig.json:
// tsconfig.json for ESM
{
"compilerOptions": {
"target": "ES2022",
"module": "node16",
"moduleResolution": "node16"
}
}npx ts-node --esm src/index.ts
Alternatively, use the ESM loader directly:
node --loader ts-node/esm src/index.ts
package.json has "type": "module", you are in ESM mode. Make sure your tsconfig.json uses"module": "node16" or "nodenext" and that your imports include the .js extension (TypeScript resolves these to .tsat compile time).tsx — The Faster Alternative
tsx is a drop-in replacement for ts-node that uses esbuild under the hood
instead of tsc. The result is dramatically faster startup:
ts-node: ~2–5 seconds on a large projecttsx: ~50–200ms on the same project
The tradeoff: tsx does not type-check. It just strips the TypeScript syntax and
runs the code. This is usually fine for development, where your editor provides
real-time type feedback.
# Install npm install --save-dev tsx # Run a TypeScript file npx tsx src/index.ts # Watch mode — restarts when files change npx tsx watch src/index.ts # Use as a shebang in scripts #!/usr/bin/env npx tsx
{
"scripts": {
"dev": "tsx watch src/index.ts",
"start": "tsx src/index.ts",
"type-check": "tsc --noEmit"
}
}tsx: use tsx for running code (fast), and a separate tsc --noEmit script for type-checking. Run the type check in CI and pre-commit hooks.tsx vs ts-node — Feature Comparison
Feature | ts-node | tsx |
|---|---|---|
Under the hood | TypeScript compiler (tsc) | esbuild |
Type checking | Yes (default) | No |
Startup speed | Moderate (~2-5s) | Very fast (~50-200ms) |
ESM support | Via --esm flag | Native, out of the box |
Watch mode | Via nodemon | Built-in (tsx watch) |
tsconfig respect | Full | Partial (paths, target) |
Decorators | Yes | Yes (esbuild handles them) |
Install size | Larger (bundles tsc) | Smaller (bundles esbuild) |
TypeScript with webpack
For frontend projects not using Vite, webpack + ts-loader is a proven combination:
npm install --save-dev webpack webpack-cli ts-loader typescript
// webpack.config.js
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};ts-loader uses the TypeScript compiler, so you get full type-checking during
builds. For faster builds without type-checking, use esbuild-loader or
babel-loader with @babel/preset-typescript instead.
TypeScript with Vite
Vite has first-class TypeScript support built in — no configuration needed. Create a Vite + TypeScript project:
npm create vite@latest my-app -- --template react-ts cd my-app npm install npm run dev
Vite uses esbuild to transpile TypeScript (fast, no type checking) during development. For production builds, it uses Rollup. Add a separate type-check step:
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"type-check": "tsc --noEmit",
"preview": "vite preview"
}
}Vite respects tsconfig.json for path aliases and module settings. The typical Vite
tsconfig for a React project:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["src"]
}TypeScript with esbuild
esbuild is an extremely fast JavaScript/TypeScript bundler and transpiler written in
Go. It does not type-check — it strips TypeScript syntax and outputs JavaScript.
npm install --save-dev esbuild
# Bundle and transpile to a single file npx esbuild src/index.ts --bundle --outfile=dist/index.js # Target a specific platform npx esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js # Watch mode npx esbuild src/index.ts --bundle --watch --outfile=dist/index.js
{
"scripts": {
"build": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js",
"dev": "esbuild src/index.ts --bundle --platform=node --watch --outfile=dist/index.js",
"type-check": "tsc --noEmit"
}
}TypeScript with Next.js
Next.js has built-in TypeScript support — just create .ts or .tsx files. Next.js
uses SWC (a Rust-based compiler) to transpile TypeScript extremely fast.
# Create a new Next.js project with TypeScript npx create-next-app@latest my-app --typescript # Or add TypeScript to an existing Next.js project # Just rename a file to .ts or .tsx and run: npm run dev # Next.js will auto-create tsconfig.json and install @types/react
Next.js transpiles TypeScript but does not block builds on type errors by default. Add type checking to your CI pipeline:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"type-check": "tsc --noEmit"
}
}Bun — Native TypeScript Runner
Bun is a modern JavaScript runtime (like Node.js) that runs TypeScript natively without any extra tools or configuration:
# Install Bun curl -fsSL https://bun.sh/install | bash # Run TypeScript directly bun run src/index.ts # Watch mode bun --watch run src/index.ts # Run a script from package.json bun run dev
Bun uses JavaScriptCore (Safari's engine) and transpiles TypeScript with its own built-in transpiler — extremely fast, no type-checking. It also handles npm packages and has a built-in test runner.
Node.js 22.6+ — Built-in TypeScript Support
Node.js 22.6 introduced experimental support for running TypeScript files directly without any external tools. Node 23+ makes this more stable:
# Node.js 22.6+ node --experimental-transform-types src/index.ts # Node.js 23+ (more stable, still experimental) node --experimental-strip-types src/index.ts
Important limitations of the built-in Node.js TypeScript support:
- Only strips types — no type-checking
- Does not support TypeScript-specific syntax like
enumornamespacewith--experimental-strip-types(use--experimental-transform-typesfor those) - Decorators and parameter properties may not work
- Still marked experimental — do not use in production yet
tsx, ts-node, or compile with tsc first.Tradeoffs Summary
The fundamental tradeoff in every TypeScript runner is type safety vs speed:
- Full type-checking (
tsc,ts-nodedefault): slower but catches type errors before runtime. Use for CI, production builds, and scripts where correctness matters. - Transpile-only (
tsx, esbuild, Vite dev, Bun, Next.js): extremely fast but no type safety at run time. Use for development and when your editor/IDE provides real-time type feedback. Always pair with a separatetsc --noEmitin CI.
The best production setup for most projects:
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"type-check": "tsc --noEmit",
"lint": "eslint src/**/*.ts"
}
}dev — tsx (fast iteration, no type-check overhead)
build — tsc (full compile + type check for production)
start — plain node (run compiled output)
type-check — tsc --noEmit (run in CI and pre-commit)
Choosing the Right Tool
Next.js / Vite project? — TypeScript is built in. Add tsc --noEmit for CI.
Node.js API or CLI? — Use tsx for development, tsc for production builds.
One-off scripts? — tsx or bun run are simplest.
Need full type checking at runtime? — ts-node (default mode).
Monorepo with complex build? — tsc with project references.
Maximum build speed? — esbuild or bun.
ts-node with full type checking, to tsx for fast development, to bundlers like Vite and webpack, to native Bun and Node.js support. The pattern to remember: use a fast transpile-only tool for development, and run tsc --noEmitin CI to catch type errors.