NodeJSts-node & tsx

ts-node & tsx

Compiling with tsc and then running node dist/index.js is correct for production but tedious in development — you don't want a manual build on every save. ts-node and tsx solve this by running TypeScript files directly, compiling on the fly, with a watch mode that restarts on change. They make the TS dev loop as fast as plain JavaScript. This page covers what each tool does, the key difference (type-checking vs pure transpilation), tsx's speed, watch mode, and the important rule: these are development tools — production still runs compiled JavaScript.

Running TypeScript directly

Bash
# tsx — fast, zero-config, the modern favorite:
npm install --save-dev tsx
npx tsx src/index.ts            # runs it directly — no build step
npx tsx watch src/index.ts     # + auto-restart on file change

# ts-node — the established option:
npm install --save-dev ts-node typescript
npx ts-node src/index.ts
Both let you run a `.ts` file directly — no separate `tsc` build — by compiling in memory as the file loads
Both tools register a hook into Node's module loading so that when a `.ts` file is imported or run, it's **transpiled to JavaScript in memory** and executed immediately — no `dist/` folder, no separate build command. You go straight from `src/index.ts` to running output. This collapses the edit-build-run cycle into edit-run, which is what you want dozens of times an hour during development. **`tsx`** (built on esbuild) is the newer, zero-config, very fast option that's become the default choice; **`ts-node`** is the older, widely-used tool that integrates tightly with your `tsconfig.json`. Both are `devDependencies` — they exist to speed up *development*, not to run in production.
The key difference — type-checking vs transpiling

tsx (esbuild)

ts-node (default)

ts-node --transpile-only

Speed

Very fast

Slower

Fast

Type-checks while running?

No

Yes

No

Catches type errors at runtime

No — it just strips types

Yes — refuses to run on errors

No

Setup

Zero-config

Reads tsconfig

Reads tsconfig

`tsx` (and `ts-node --transpile-only`) do NOT type-check — they strip types and run, so a separate `tsc --noEmit` gate is essential
This is the distinction that trips people up. **`tsx`** and esbuild-based runners **transpile only** — they strip type annotations and run the JavaScript as fast as possible, *without type-checking*. That means a genuine type error (wrong argument, missing property) **won't stop `tsx` from running** your code; it'll just run until the bad value causes a runtime crash, exactly as untyped JS would. `ts-node` in its **default** mode *does* type-check and will refuse to run code with type errors — safer, but noticeably slower, which is why many people run it with `--transpile-only` for speed (giving up the checking). The takeaway: if your dev runner doesn't type-check (tsx, or ts-node transpile-only), you **must** run a separate **`tsc --noEmit`** as a checking step — in your editor, a pre-commit hook, and CI — or type errors will slip through to runtime entirely. Don't rely on the runner to catch types.
Watch mode — the fast dev loop

package.json

JSON
{
  "scripts": {
    "dev": "tsx watch src/index.ts",     // restarts the process on any source change
    "build": "tsc",                      // production build → dist/
    "start": "node dist/index.js",       // production run (compiled JS)
    "typecheck": "tsc --noEmit"          // the type-checking gate (since tsx skips it)
  }
}
`tsx watch` (or `ts-node` + nodemon) restarts your app on save — pair it with `tsc --noEmit` for type safety
For an iterative server or script, **`tsx watch`** is the one-stop dev command: it runs your entry file and **restarts the process whenever a source file changes**, so you see results instantly on save — built in, no extra tooling. (With `ts-node` you typically pair it with [`nodemon`](/nodejs/nodemon) to get the same restart-on-change behavior.) Because `tsx` skips type-checking for speed, the complete setup is a **two-track** workflow: `npm run dev` (`tsx watch`) for the fast feedback loop, plus `npm run typecheck` (`tsc --noEmit`) running in your editor and CI to actually enforce types. You get JS-like iteration speed *and* full type safety — just from two complementary commands instead of one.
Don't ship these to production
Run compiled JS in production, not `tsx`/`ts-node` — on-the-fly transpilation adds startup cost and a dev dependency in your runtime path
These tools are for **development**; production should run the **compiled JavaScript** from `tsc`. Running `tsx` or `ts-node` in production means transpiling your code *on every startup* (slower boots, more memory), putting a build tool in your runtime dependency path (a larger attack surface and more to break), and — for `ts-node` default mode — re-type-checking at boot, which is pure waste when the build already verified it. The correct production flow is: `tsc` compiles `src/` → `dist/` during the build/CI step, and you run `node dist/index.js`. The compiled output is plain, fast, dependency-light JavaScript with no transpilation overhead. Reserve `tsx`/`ts-node` for `npm run dev`, scripts, and one-off TypeScript execution — never the deployed process. (Recent Node versions can also run `.ts` directly via type-stripping, but the build-and-run-JS model remains the safe production default.)
Next
Manage settings and secrets across environments: [Configuration Management](/nodejs/config-intro).