13.1 Node.js TS Environment — tsx, ts-node, @types/node Setup
Node.js TypeScript Environment Configuration
There are three main ways to use TypeScript with Node.js:
| Method | Description | When to Use |
|---|---|---|
tsx | esbuild-based, fast, no type checking | Dev server, scripts |
ts-node | tsc-based, includes type checking, slower | Simple execution |
tsc + node | Build then run, production-safe | Production deployment |
Installing @types/node
npm install --save-dev @types/node typescript
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS", // or NodeNext
"moduleResolution": "node", // or NodeNext
"lib": ["ES2022"],
"types": ["node"], // Include Node.js types
"strict": true,
"esModuleInterop": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Setting Up Dev Environment with tsx
npm install --save-dev tsx
// package.json
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"typecheck": "tsc --noEmit"
}
}
// src/index.ts
import { createServer } from 'http'
import { readFileSync } from 'fs'
import path from 'path'
// Full type support thanks to @types/node
const port = parseInt(process.env.PORT ?? '3000')
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ message: 'Hello TypeScript!' }))
})
server.listen(port, () => {
console.log(`Server running: http://localhost:${port}`)
})
ESM Node.js Setup
// package.json
{
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
// tsconfig.json (ESM)
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"outDir": "./dist"
}
}
// .js extension is required in ESM
import { helper } from './utils/helper.js'
import express from 'express'
Module Resolution Configuration Comparison
// CJS (traditional Node.js)
{
"module": "CommonJS",
"moduleResolution": "node"
}
// ESM (modern Node.js)
{
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
The exports field in package.json
{
"name": "my-package",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
}
}
Practical Node.js Project Structure
my-api/
├── src/
│ ├── index.ts # Entry point
│ ├── config/
│ │ └── env.ts # Environment variables
│ ├── middleware/
│ │ ├── auth.ts
│ │ └── error.ts
│ ├── routes/
│ │ └── user.ts
│ ├── services/
│ │ └── user.service.ts
│ ├── repositories/
│ │ └── user.repo.ts
│ └── types/
│ └── index.ts
├── tsconfig.json
└── package.json
// src/index.ts
import express from 'express'
import { env } from './config/env'
import { userRouter } from './routes/user'
import { errorMiddleware } from './middleware/error'
const app = express()
app.use(express.json())
app.use('/api/users', userRouter)
app.use(errorMiddleware)
app.listen(env.PORT, () => {
console.log(`🚀 Server running: http://localhost:${env.PORT}`)
})
Pro Tips
Built-in ESM support by Node.js version
Node.js 12+: Experimental ESM support
Node.js 16+: Stable ESM support
Node.js 18+: Built-in fetch API
Node.js 20+: Recommended LTS
Match type versions
# Install @types/node matching your Node.js version
npm install --save-dev @types/node@20 # Node.js 20 LTS