
Running TypeScript with ts-node – Setup and Usage
TypeScript has become the go-to language for building robust JavaScript applications, but let’s be honest – the compile step can be a real pain during development. That’s where ts-node comes in, allowing you to run TypeScript files directly without the tedious build process. This guide will walk you through everything from basic setup to advanced configurations, common gotchas you’ll inevitably encounter, and how to squeeze the best performance out of your development workflow.
What is ts-node and How Does It Work
ts-node is a TypeScript execution engine and REPL for Node.js that transforms TypeScript into JavaScript on-the-fly. Instead of running tsc
to compile your .ts files and then executing the resulting .js files, ts-node handles both steps seamlessly in memory.
Under the hood, ts-node hooks into Node.js’s module loading system using require.extensions
and the newer ES module loader hooks. When you import or require a TypeScript file, ts-node intercepts the request, compiles the TypeScript code using the TypeScript compiler API, and feeds the resulting JavaScript directly to Node.js for execution.
The magic happens through these key components:
- TypeScript compiler API integration for real-time compilation
- Source map support for accurate error reporting and debugging
- Configurable transpilation options through tsconfig.json
- Module resolution that works with both CommonJS and ES modules
- Built-in REPL for interactive TypeScript development
Installation and Basic Setup
Getting ts-node up and running is straightforward, but there are a few different approaches depending on your needs. Here’s the most common setup:
# Install TypeScript and ts-node globally (quick start)
npm install -g typescript ts-node
# Or install locally in your project (recommended)
npm install --save-dev typescript ts-node
npm install --save-dev @types/node # Node.js type definitions
For a local installation, you’ll want to add scripts to your package.json:
{
"scripts": {
"dev": "ts-node src/index.ts",
"start": "node dist/index.js",
"build": "tsc"
},
"devDependencies": {
"@types/node": "^20.0.0",
"ts-node": "^10.9.0",
"typescript": "^5.0.0"
}
}
Create a basic tsconfig.json file to configure TypeScript compilation:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModules": true
},
"ts-node": {
"files": true,
"experimentalSpecifierResolution": "node"
}
}
Now you can run TypeScript files directly:
# Run a TypeScript file
npx ts-node src/app.ts
# Start the REPL
npx ts-node
# Run with specific TypeScript config
npx ts-node --project tsconfig.dev.json src/app.ts
Advanced Configuration and Performance Tuning
The default ts-node configuration works fine for small projects, but you’ll want to optimize it for larger applications. Here are the key configuration options that actually matter:
{
"ts-node": {
"files": true,
"transpileOnly": true,
"compilerOptions": {
"module": "CommonJS",
"target": "ES2020"
},
"ignore": ["node_modules"],
"preferTsExts": true
}
}
The transpileOnly
option is a game-changer for performance. It skips type checking during execution, which can speed up startup times significantly:
Configuration | Startup Time | Type Safety | Best For |
---|---|---|---|
Default (with type checking) | ~2-5 seconds | Full | Small projects |
transpileOnly: true | ~0.5-1 second | Runtime only | Development |
SWC integration | ~0.2-0.5 seconds | Runtime only | Large projects |
For even better performance, consider using SWC as the transpiler:
npm install --save-dev @swc/core @swc/helpers regenerator-runtime
{
"ts-node": {
"swc": true
}
}
Real-World Examples and Use Cases
Let’s look at some practical scenarios where ts-node shines. Here’s a typical Express.js setup with TypeScript:
// src/server.ts
import express from 'express';
import { Request, Response } from 'express';
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.get('/health', (req: Request, res: Response) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
export default app;
For database operations with proper typing:
// src/database.ts
import { Pool } from 'pg';
interface User {
id: number;
email: string;
created_at: Date;
}
class Database {
private pool: Pool;
constructor() {
this.pool = new Pool({
connectionString: process.env.DATABASE_URL
});
}
async getUser(id: number): Promise {
const result = await this.pool.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result.rows[0] || null;
}
}
export { Database, User };
Running scripts with ts-node is particularly useful for data migration and maintenance tasks:
// scripts/migrate.ts
import { Database } from '../src/database';
async function runMigration() {
const db = new Database();
try {
console.log('Starting migration...');
// Your migration logic here
console.log('Migration completed successfully');
} catch (error) {
console.error('Migration failed:', error);
process.exit(1);
}
}
runMigration();
# Run the migration
npx ts-node scripts/migrate.ts
Integration with Development Tools
ts-node plays well with most development tools. Here’s how to set it up with popular options:
Nodemon for auto-restart:
npm install --save-dev nodemon
// nodemon.json
{
"watch": ["src"],
"ext": "ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "ts-node src/index.ts"
}
Jest for testing:
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
};
Docker integration:
# Dockerfile.dev
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npx", "ts-node", "src/index.ts"]
Common Issues and Troubleshooting
Here are the most frequent problems you’ll encounter and their solutions:
Module Resolution Issues:
If you’re getting “Cannot find module” errors, especially with path mapping:
npm install --save-dev tsconfig-paths
npx ts-node -r tsconfig-paths/register src/index.ts
Or configure it in tsconfig.json:
{
"ts-node": {
"require": ["tsconfig-paths/register"]
}
}
ES Module Issues:
For projects using ES modules, you’ll need proper configuration:
// package.json
{
"type": "module"
}
// tsconfig.json
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node"
},
"ts-node": {
"esm": true
}
}
Performance Problems:
Large codebases can suffer from slow startup times. Enable compiler optimizations:
{
"ts-node": {
"transpileOnly": true,
"files": false,
"experimentalSpecifierResolution": "node"
}
}
Type Checking in Production:
Never use ts-node in production. Instead, compile your code and run the JavaScript:
# Build process
npm run build
node dist/index.js
Alternatives and When to Use Them
While ts-node is excellent for development, there are alternatives worth considering:
Tool | Speed | Type Checking | Best Use Case |
---|---|---|---|
ts-node | Medium | Full | Development, scripts |
tsx | Fast | None | Quick prototyping |
Bun | Very Fast | Basic | Modern runtime alternative |
esbuild + tsc | Very Fast | Separate step | Build pipelines |
tsx is a lighter alternative built on esbuild:
npm install --save-dev tsx
npx tsx src/index.ts
Bun has built-in TypeScript support:
bun install
bun run src/index.ts
Best Practices and Production Considerations
Here are the key practices that will save you headaches:
- Always use
transpileOnly: true
in development and run type checking separately withtsc --noEmit
- Set up proper path mapping in tsconfig.json instead of using relative imports
- Use environment-specific configurations for different stages
- Never deploy ts-node to production – compile your code first
- Configure your hosting environment properly for optimal performance
For deployment on a VPS or dedicated server, your build process should look like this:
# Production build script
#!/bin/bash
npm ci --production=false
npm run build
npm prune --production
node dist/index.js
Environment-specific configurations help maintain consistency:
// tsconfig.dev.json
{
"extends": "./tsconfig.json",
"ts-node": {
"transpileOnly": true,
"files": false
}
}
The key to successful TypeScript development with ts-node is understanding when to use it and when to reach for alternatives. It’s an incredibly powerful tool that can significantly speed up your development workflow when configured properly, but it’s not a silver bullet for every situation.
For more detailed configuration options and advanced use cases, check out the official ts-node documentation and the TypeScript handbook.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.