Skip to main content

TypeScript Support

Node.js provides built-in support for running TypeScript code through type stripping. This allows you to run TypeScript files directly without a separate compilation step.

Enabling TypeScript Support

There are two ways to enable TypeScript support in Node.js:

1. Full TypeScript Support

For full support of all TypeScript features, including tsconfig.json, use a third-party package like tsx:
# Install tsx
npm install --save-dev tsx

# Run TypeScript files
npx tsx your-file.ts

# Or with node
node --import=tsx your-file.ts
This approach provides:
  • Full TypeScript feature support
  • tsconfig.json configuration
  • Any TypeScript version compatibility
  • Code transformation (enums, namespaces, etc.)

2. Built-in Type Stripping

Node.js includes built-in type stripping that is enabled by default. This provides a lightweight way to run TypeScript:
node your-file.ts
To disable type stripping:
node --no-strip-types your-file.ts

Type Stripping

Type stripping is a lightweight feature that:
  • Removes TypeScript type annotations
  • Replaces types with whitespace
  • Does not perform type checking
  • Does not transform code
  • Does not require source maps

Supported Features

Type stripping supports:
  • Type annotations
  • Interfaces
  • Type aliases
  • Type-only imports/exports
  • Generic type parameters
  • Type assertions
  • Type-only namespaces

Unsupported Features

Features that require code transformation are not supported:
  • enum declarations
  • namespace with runtime code
  • Parameter properties
  • Import aliases
  • Decorators (not yet standardized)
For optimal compatibility with type stripping, use these settings:
{
  "compilerOptions": {
    "noEmit": true,
    "target": "esnext",
    "module": "nodenext",
    "rewriteRelativeImportExtensions": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true
  }
}

Module System

Node.js supports both CommonJS and ES Modules in TypeScript:

File Extensions

  • .ts files - Module system determined like .js files (use "type": "module" in package.json for ESM)
  • .mts files - Always ES modules (like .mjs)
  • .cts files - Always CommonJS (like .cjs)
  • .tsx files - Not supported

Import/Require Syntax

// ES Modules (.ts with "type": "module" or .mts)
import { readFile } from 'node:fs/promises';
import { User } from './user.ts';  // Include .ts extension!

export async function loadUser(id: string): Promise<User> {
  const data = await readFile(`users/${id}.json`);
  return JSON.parse(data.toString());
}

// CommonJS (.ts without "type": "module" or .cts)
const fs = require('node:fs');
const { User } = require('./user.ts');  // Include .ts extension!

module.exports = {
  loadUser(id: string): User {
    const data = fs.readFileSync(`users/${id}.json`);
    return JSON.parse(data.toString());
  }
};
Important: File extensions are mandatory in imports and requires:
  • import './file.ts'
  • import './file'
  • require('./file.ts')
  • require('./file')

Type Imports

Use the type keyword for type-only imports:
// Correct: Using 'type' keyword
import type { User, Post } from './types.ts';
import { fetchData, type ApiResponse } from './api.ts';

// Incorrect: Missing 'type' keyword
import { User, Post } from './types.ts';  // Runtime error!
The verbatimModuleSyntax tsconfig option enforces this pattern.

Examples

Basic TypeScript File

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function multiply(a: number, b: number): number {
  return a * b;
}

export type MathOperation = 'add' | 'multiply';
Run it:
node math.ts

Using Interfaces

// user.ts
interface User {
  id: string;
  name: string;
  email: string;
  age?: number;
}

function createUser(name: string, email: string): User {
  return {
    id: Math.random().toString(36),
    name,
    email
  };
}

const user = createUser('John Doe', '[email protected]');
console.log(user);

Generic Functions

// utils.ts
function identity<T>(value: T): T {
  return value;
}

function filter<T>(array: T[], predicate: (item: T) => boolean): T[] {
  return array.filter(predicate);
}

const numbers = [1, 2, 3, 4, 5];
const evens = filter(numbers, n => n % 2 === 0);
console.log(evens); // [2, 4]

Type Assertions

// api.ts
import type { IncomingMessage } from 'node:http';

function getStatusCode(response: IncomingMessage): number {
  return (response as any).statusCode as number;
}

Working with Node.js APIs

// server.ts
import { createServer } from 'node:http';
import type { IncomingMessage, ServerResponse } from 'node:http';

interface RequestHandler {
  (req: IncomingMessage, res: ServerResponse): void;
}

const handler: RequestHandler = (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello TypeScript!');
};

const server = createServer(handler);
server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

Non-File Inputs

Type stripping works with --eval and STDIN:
# Evaluate TypeScript code
node --eval "const x: number = 42; console.log(x)"

# From STDIN
echo "const y: string = 'hello'; console.log(y)" | node --input-type=module
Note: TypeScript is not supported in:
  • REPL (interactive mode)
  • --check (syntax checking)
  • inspect (debugger)

Source Maps

Since type stripping only removes types (replacing them with whitespace), source maps are not needed for accurate stack traces. Line numbers in TypeScript files match the stripped JavaScript output.

Dependencies and node_modules

Node.js refuses to process TypeScript files inside node_modules directories. This encourages package authors to publish transpiled JavaScript rather than TypeScript source. If you need TypeScript in dependencies:
  1. Publish packages as JavaScript
  2. Include type definitions (.d.ts files)
  3. Reference types via @types/ packages

Path Aliases

TypeScript’s paths configuration in tsconfig.json is not supported. Instead, use Node.js subpath imports:
// package.json
{
  "imports": {
    "#utils/*": "./src/utils/*",
    "#types/*": "./src/types/*"
  }
}
// Use in code
import { helper } from '#utils/helper.ts';
import type { User } from '#types/user.ts';
Note: Subpath imports must start with #.

Limitations

No tsconfig.json Processing

Node.js does not read tsconfig.json, so features depending on it are unsupported:
  • Path mapping (paths)
  • Base URL (baseUrl)
  • Target transpilation (target)
  • JSX transformation
  • Module resolution strategies

No Code Transformation

These TypeScript features require code generation and are not supported:
// ❌ Enums (require transformation)
enum Color {
  Red,
  Green,
  Blue
}

// ❌ Namespaces with runtime code
namespace Utils {
  export function helper() {
    return 42;
  }
}

// ❌ Parameter properties
class User {
  constructor(public name: string) {}
}

// ✅ Alternatives that work
const Color = {
  Red: 0,
  Green: 1,
  Blue: 2
} as const;

export function helper() {
  return 42;
}

class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

Best Practices

  1. Use type imports - Always use import type for types:
    import type { User } from './types.ts';
    
  2. Include file extensions - Always include .ts in imports:
    import { helper } from './utils.ts';
    
  3. Avoid transformation features - Don’t use enums, namespaces with runtime code, etc.
  4. Enable strict mode - Use strict TypeScript settings for better type safety:
    {
      "compilerOptions": {
        "strict": true
      }
    }
    
  5. Use @types packages - Install type definitions for external packages:
    npm install --save-dev @types/node
    
  6. Publish as JavaScript - If creating packages, publish transpiled JS with .d.ts files
  7. Test both modes - Test your code with both type stripping and full compilation

When to Use Each Approach

Use Built-in Type Stripping When:

  • Building simple scripts or tools
  • Working with modern TypeScript syntax
  • Want minimal setup and fast startup
  • Don’t need advanced TypeScript features
  • Creating Node.js applications (not libraries)

Use Full TypeScript (tsx, ts-node, etc.) When:

  • Need enums, namespaces, or decorators
  • Require tsconfig.json features
  • Building libraries for npm
  • Need compatibility with older TypeScript versions
  • Want full type checking during development
  • Creating browser-targeted code

Additional Resources