Effortless TypeScript Development: A Guide to ts-node | Ctrix | Tech Blog

Effortless TypeScript Development: A Guide to ts-node

typescript

nodejs

The standard TypeScript workflow often involves a two-step process: compile your .ts files into .js with the TypeScript Compiler (tsc), and then run the resulting JavaScript files with Node.js. While this is great for production builds, it can slow down development and add a layer of complexity to your workflow.

What if you could run your TypeScript files directly, without the explicit compilation step? Enter ts-node.

What is ts-node?

ts-node is a TypeScript execution engine and REPL for Node.js. It’s a development tool that JIT (Just-In-Time) transforms TypeScript into JavaScript, allowing you to run TypeScript code directly on the Node.js runtime. It’s the magic that makes commands like ts-node my-script.ts possible.

Why Use ts-node?

The primary benefit of ts-node is a streamlined development workflow.

  • Rapid Iteration: Save a file and immediately run it without waiting for a full project compilation.
  • Simplified Tooling: No need for complex tsc --watch and nodemon setups running in separate terminals.
  • Easy Scripting: Perfect for writing and running utility scripts, database migrations, or tests in TypeScript.
  • Seamless Integration: It automatically reads your tsconfig.json, respecting your project’s compiler options.

Setting Up Your Project

Let’s walk through setting up a Node.js project to use ts-node.

1. Installation

First, you’ll need to add typescript, ts-node, and the necessary type definitions for Node.js to your project. Since your project uses pnpm, you can use the following command:

pnpm add typescript ts-node @types/node --save-dev

2. Configuring tsconfig.json

ts-node needs a tsconfig.json file to understand how to handle your code. This file is crucial for telling the compiler which files to include and which rules to follow.

Create a tsconfig.json file in your project root:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    /* Add this to handle custom type declarations */
    "typeRoots": ["./node_modules/@types", "./src/types"]
  },
  /* This is the key to including all your code */
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

The Compiler’s GPS: Mapping Your Codebase with tsconfig.json

A tsconfig.json file is more than just configuration; it’s the brain of your project, telling the compiler how to behave. To unlock its full power, you need to teach it where to look. This ensures that every type, interface, and module you create is automatically recognized.

Here’s how you give your compiler perfect vision:

1. The “include” Property: Your Project’s Blueprint

Think of the include array as a map you’re handing to TypeScript. By setting "include": ["src/**/*"], you’re giving it a simple, powerful instruction: “Scan every single file inside the src folder and all its subfolders.”

This single line is the key to making your project-wide types available everywhere, preventing those frustrating “Cannot find name…” errors.

2. The “typeRoots” Property: Your Custom Knowledge Base

Sometimes, you’ll work with third-party libraries that don’t have built-in types, or you’ll want to define your own global types in .d.ts files. How do you make TypeScript aware of them?

This is where typeRoots comes in. It lets you define a list of trusted locations for type definitions.

"typeRoots": ["./node_modules/@types", "./src/types"]
  • ./node_modules/@types: This is the standard location where types from packages like @types/node are installed. You should always include it.
  • ./src/types: This is your secret weapon. By adding a custom types folder to your source directory, you create a centralized place for all your custom declaration files. TypeScript will now automatically load any .d.ts file you put here, effectively extending its knowledge base with your project-specific types.

With these settings, you’ve elevated your tsconfig.json from a simple config file to a smart, context-aware blueprint of your entire codebase.

3. Setting Up package.json Scripts

To make running your server easy, you can add a script to your package.json. For this example, let’s assume your server’s entry point is src/server.ts.

For a better development experience, we’ll also use nodemon, which automatically restarts the server when it detects file changes.

First, install nodemon:

pnpm add nodemon --save-dev

Now, add the following script to your package.json:

{
  "scripts": {
    "dev": "nodemon --exec ts-node src/server.ts"
  }
}

How it works:

  • nodemon: Watches for file changes in your directory.
  • --exec ts-node src/server.ts: When a change is detected, nodemon will execute this command. It tells ts-node to run your main server file.

Now, you can start your server with a single command:

pnpm dev

Your TypeScript server will start, and anytime you save a file in the src directory, it will automatically restart with the latest changes.

Conclusion

ts-node is an indispensable tool for modern TypeScript development. It removes the friction of the compile-and-run cycle, allowing you to focus on writing code. By correctly configuring your tsconfig.json to include all your source files and and setting up a simple package.json script, you can create a powerful and efficient development environment.

While ts-node is perfect for development, remember that for production, it’s still best practice to compile your code to optimized JavaScript and run that directly with Node.js.

Latest Posts

Enjoyed this article? Follow me on X for more content and updates!

Follow @Ctrixdev