On this page
Setup and tsconfig.json
Setting up TypeScript
TypeScript can be installed globally or locally per project. For any serious project, install it locally — this ensures every contributor uses the same version and your CI environment is reproducible.
# Create a new project directory
mkdir my-ts-project && cd my-ts-project
# Initialize npm
npm init -y
# Install TypeScript as a dev dependency
npm install -D typescript
# Verify the installed version
npx tsc --version # Version 6.0.xOnce installed, create a source file to verify everything works:
// src/index.ts
const greeting: string = "Hello, TypeScript 6.0!";
console.log(greeting);Compile and run it:
npx tsc src/index.ts --target ES2022
node src/index.jsThe tsconfig.json file
For any project with more than one file, you need a tsconfig.json. This file tells the compiler where to find source files, where to put the output, and which rules to enforce. Generate a starting point with:
npx tsc --initThis creates a tsconfig.json with every option commented out. The example in the code panel shows a production-ready configuration for a Node.js or full-stack project. Let us walk through each important option.
Target and module
target controls the JavaScript version that tsc outputs. Modern projects should use ES2022 or later, which preserves native async/await, optional chaining, and class fields without polyfilling them.
module controls how import/export statements are compiled. The correct value depends on where your code runs:
| Environment | Recommended module |
|---|---|
| Node.js 18+ | NodeNext |
| Browser (bundled by Vite/esbuild) | ESNext |
| Browser (no bundler) | ES2022 |
| Legacy CommonJS Node.js | CommonJS |
moduleResolution must match module. When module is NodeNext, set moduleResolution to NodeNext as well. This enables Node.js-style resolution with support for the exports field in package.json, subpath imports, and .js extensions in TypeScript imports.
// With NodeNext resolution, imports must use the .js extension
// (TypeScript resolves .ts files but the compiled output uses .js)
import { formatDate } from "./utils/date.js";The strict flag
strict: true is the single most important option in any tsconfig.json. It is a shorthand that enables eight individual strict checks simultaneously. The most impactful one is strictNullChecks, which makes null and undefined distinct types rather than assignable to everything:
// Without strictNullChecks — dangerous
function getLength(text: string): number {
return text.length; // what if text is null?
}
// With strictNullChecks — safe
function getLength(text: string | null): number {
if (text === null) return 0;
return text.length; // TypeScript knows text is string here
}Without strictNullChecks, TypeScript misses an entire category of null pointer errors. Always enable strict.
outDir and rootDir
These two options keep your project organized:
rootDir— the root folder of your source files (usually./src). TypeScript will mirror this folder structure in the output.outDir— where compiled JavaScript files are written (usually./dist).
With rootDir: "./src" and outDir: "./dist", the file src/app/server.ts compiles to dist/app/server.js.
esModuleInterop
This option generates helper code that allows you to import CommonJS modules (like those from npm) using default import syntax:
// Without esModuleInterop — verbose
import * as path from "path";
// With esModuleInterop — natural
import path from "path";Always enable this for Node.js projects. Most modern TypeScript starter templates include it.
paths — module aliases
The paths option maps import aliases to real file locations, eliminating long relative imports deep in nested folders:
// Without paths — fragile and hard to read
import { UserService } from "../../../services/user.service";
// With paths configured — clean and refactor-safe
import { UserService } from "@app/services/user.service";The tsconfig.json configuration for this is shown in the code example. Remember that paths only affects the TypeScript compiler — you need matching alias configuration in your bundler or Node.js loader.
declaration and source maps
For libraries published to npm, enable declaration: true to generate .d.ts type definition files alongside the compiled JavaScript. Consumers of your library get full autocomplete without needing access to your source.
declarationMap: true generates .d.ts.map files that allow IDE "go to definition" to jump to the original TypeScript source instead of the generated declaration file.
sourceMap: true generates .js.map files that map compiled JavaScript lines back to the original TypeScript source. This makes stack traces in Node.js and browser DevTools readable.
Running TypeScript in Node.js
For development scripts and backend applications you often want to run TypeScript files directly without a separate compile step.
tsx is the recommended tool:
npm install -D tsx
# Run a TypeScript file directly
npx tsx src/server.ts
# Watch mode — restart on file changes
npx tsx watch src/server.tstsx uses esbuild internally and is extremely fast. It skips type checking (use tsc --noEmit separately for that) and focuses purely on execution speed.
ts-node is the older alternative. It is slower but supports more configuration options and is sometimes required by tools that rely on the Node.js require hook:
npm install -D ts-node @types/node
# Run with ts-node
npx ts-node src/server.ts
# Run with the faster SWC transpiler
npx ts-node --swc src/server.tsProject references for monorepos
Large monorepos with multiple TypeScript packages benefit from project references, which allow incremental compilation across packages:
// packages/api/tsconfig.json
{
"compilerOptions": {
"composite": true,
"outDir": "./dist"
},
"references": [
{ "path": "../shared" }
]
}With composite: true, TypeScript builds only the packages that changed since the last build. Running tsc --build (or tsc -b) resolves the dependency graph and compiles packages in the correct order.
A minimal but production-ready setup
For a new project, here is a minimal workflow that works immediately:
npm init -y
npm install -D typescript tsx @types/node
# Generate tsconfig.json, then configure it
npx tsc --initEdit the generated tsconfig.json to match the example in the code panel, then add these scripts to package.json:
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit",
"start": "node dist/index.js"
}
}This gives you fast iteration during development (dev), strict type checking on demand (typecheck), and a clean compiled output for production (build + start).
Practice
- Create a new project, install TypeScript, and run
npx tsc --init. Compare the generated file to the example in this lesson. Identify three options that are disabled by default but should be enabled. - Write a small TypeScript file that imports a built-in Node.js module (e.g.,
pathorfs). Try compiling it both with and withoutesModuleInterop. Observe the difference in the compiler output. - Add a
pathsalias to your tsconfig.json, create a file at the aliased location, and import it using the alias. Verify that the compiler resolves it correctly.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"paths": {
"@app/*": ["./src/app/*"],
"@utils/*": ["./src/utils/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
Sign in to track your progress