Installing TypeScript #
TypeScript isn’t a standalone language — it’s a superset of JavaScript that must be compiled before it can run on any runtime. That means you need the right toolchain from the start: Node.js as the runtime, npm as the package manager, and the TypeScript compiler (tsc) as the bridge between the .ts code you write and the JavaScript the machine executes. The setup process looks simple, but many developers miss important details — especially the difference between global and local installation, and how tsconfig.json controls the compiler’s entire behavior. This article walks you from zero to a TypeScript environment ready for real projects.
Prerequisites: Node.js and npm #
TypeScript is installed via npm, which makes Node.js the foundation of this entire toolchain. Before going further, make sure you understand what you’re actually installing — npm is bundled with Node.js, so you don’t need to install it separately.
Visit nodejs.org and download the LTS (Long Term Support) version. LTS versions receive long-term security updates and are far more stable for day-to-day development than the Current release, which contains experimental features. At the time of writing, Node.js LTS is at version 20.x.
After installation, verify that both are properly installed:
node -v
# v20.x.x
npm -v
# 10.x.x
If either command produces a command not found error, the installation didn’t complete correctly. On Linux/macOS, make sure the Node.js installation directory is on your PATH. On Windows, try closing and reopening the terminal after installation.
Don’t use Node.js installed from your system’s package manager (likeapton Ubuntu orbrewon macOS) for development — the versions often lag far behind. Use nvm (Node Version Manager) so you can switch between Node.js versions easily, especially if you manage many projects with different Node.js requirements.
Optional: Use nvm for Version Management #
If you work on more than one TypeScript project, it’s highly recommended to start with nvm:
# Install nvm (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# Install the latest Node.js LTS
nvm install --lts
# Set as default
nvm alias default node
# Verify
node -v
With nvm, switching Node.js versions between projects is as easy as running nvm use 18 or nvm use 20.
Installing TypeScript: Global vs Local #
This is the first decision you have to make, and it’s often misunderstood. There are two ways to install TypeScript: globally (available across the entire system) or locally (only available within a specific project). Each has different use cases.
Global Installation #
A global installation puts the tsc binary on your system PATH so it can be run from any directory:
npm install -g typescript
Verify the installation:
tsc --version
# Version 5.x.x
When to use a global installation? For quick experiments, one-off scripts, or when you’re just learning TypeScript and don’t have a defined project yet. It’s also handy on your personal dev machine.
Local Installation (Per Project) #
A local installation is only available inside the project where TypeScript is installed. This is the recommended approach for team and production projects:
# Initialize a new project first
mkdir typescript-project && cd typescript-project
npm init -y
# Install TypeScript as a dev dependency
npm install --save-dev typescript
Or with Yarn:
yarn add --dev typescript
After a local installation, tsc isn’t immediately available in your terminal — you need to call it via npx or through npm scripts:
# Call tsc from the local installation
npx tsc --version
# Or define it in package.json scripts
Common anti-pattern: Relying on a global TypeScript installation for team projects. This causes serious problems when team members have different TypeScript versions — code that compiles on one machine can fail on another because of behavioral differences between versions. Always install TypeScript as adevDependencyinpackage.json.
Global vs Local Comparison #
| Aspect | Global | Local (per project) |
|---|---|---|
| Accessibility | From any directory | Only within the project |
| Version | One version for everything | Can differ per project |
| Team consistency | ✗ Not guaranteed | ✓ Guaranteed via package.json |
| CI/CD | Requires manual installation | Automatic via npm install |
| Recommendation | Experiments/learning | Real projects |
TypeScript Project Structure #
Before diving into configuration, it’s important to understand the directory structure commonly used in TypeScript projects. This isn’t a hard rule, but a widely accepted convention:
typescript-project/
├── src/ # TypeScript source (.ts)
│ ├── index.ts
│ └── utils/
│ └── helper.ts
├── dist/ # Compiled JavaScript output (.js)
├── node_modules/ # Dependencies
├── package.json
├── package-lock.json
└── tsconfig.json # TypeScript compiler configuration
The src//dist/ split is a best practice: TypeScript source doesn’t mix with JavaScript output. The dist/ directory usually goes into .gitignore since it can always be regenerated from source.
TypeScript Configuration: tsconfig.json
#
The tsconfig.json file is the control center of the TypeScript compiler. Without it, tsc uses default settings that are often suboptimal for real projects. Generate an initial config file with:
npx tsc --init
This command produces a tsconfig.json with hundreds of options, nearly all commented out. Here’s a cleaner configuration suited to modern TypeScript projects:
{
"compilerOptions": {
// Target runtime and output
"target": "ES2022",
"module": "CommonJS",
"lib": ["ES2022"],
// Input and output directories
"rootDir": "./src",
"outDir": "./dist",
// Type safety
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
// Code quality
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
// Interoperability
"esModuleInterop": true,
"resolveJsonModule": true,
// Source maps for debugging
"sourceMap": true,
// Don't emit if there are errors
"noEmitOnError": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
The Most Important Configuration Options #
target determines the JavaScript version of the output. ES2022 is a safe choice for Node.js 18+ and modern browsers. If you’re targeting older browsers, use ES5 or ES2015.
strict: true enables all strict type checks at once. This is the most important option — don’t disable it just because it makes you write more code. Type discipline up front saves hours of debugging later.
noEmitOnError: true ensures TypeScript doesn’t produce JavaScript files if there are compilation errors. Without this option, you could accidentally deploy code that actually contains type errors.
sourceMap: true generates .map files that connect the output JavaScript with its original TypeScript lines. This is crucial for debugging — stack traces will point to TypeScript lines, not the generated JavaScript.
Enable strict: true from day one of a project. Enabling it on an already-running project is far more painful because you have to fix hundreds or thousands of type errors at once. Starting strict from the beginning is a small investment with big returns.Writing and Compiling TypeScript Code #
With tsconfig.json configured, it’s time to write your first TypeScript code. Create src/index.ts:
// src/index.ts
// TypeScript adds explicit type annotations
const pesan: string = "Halo, TypeScript!";
const angka: number = 42;
const aktif: boolean = true;
// An interface defines the shape of an object
interface Pengguna {
id: number;
nama: string;
email: string;
}
// A function with typed parameters and an explicit return type
function buatPengguna(id: number, nama: string, email: string): Pengguna {
return { id, nama, email };
}
// TypeScript will error if types don't match
// ANTI-PATTERN: buatPengguna("satu", 123, true) — TypeScript will reject this
const pengguna = buatPengguna(1, "Budi Santoso", "[email protected]");
console.log(`Pesan: ${pesan}`);
console.log(`Pengguna:`, pengguna);
Compile this file:
npx tsc
If there are no errors, you’ll find dist/index.js and dist/index.map as the compilation output. Run the JavaScript output:
node dist/index.js
# Pesan: Halo, TypeScript!
# Pengguna: { id: 1, nama: 'Budi Santoso', email: '[email protected]' }
Watch Mode: Automatic Compilation #
During active development, running tsc manually on every change is very inefficient. Use the --watch flag for automatic compilation:
npx tsc --watch
Now every time you save a .ts file, the compiler automatically detects the change and recompiles. You’ll see output like this in your terminal:
[3:45:21 PM] Starting compilation in watch mode...
[3:45:22 PM] Found 0 errors. Watching for file changes.
Handling Compilation Errors #
One of TypeScript’s core values is detecting errors before runtime. Try creating a deliberate error:
// src/index.ts — add this line
// ANTI-PATTERN: Assigning a string value to a number variable
const nilai: number = "bukan angka";
// ^^^^^ Error: Type 'string' is not assignable to type 'number'
// CORRECT: Types must be consistent
const nilaiBenar: number = 42;
TypeScript will immediately report:
src/index.ts:XX:7 - error TS2322: Type 'string' is not assignable to type 'number'.
Found 1 error.
Because noEmitOnError: true is active, no JavaScript files are produced while an error exists. This forces you to resolve type issues before you can deploy.
The TypeScript Compilation Flow #
It’s important to understand what happens behind the scenes when you run tsc:
flowchart TD
A[.ts File] --> B[TypeScript Compiler - tsc]
B --> C{Type Checking}
C -- Has Errors --> D[Error Report]
C -- Clean --> E[JavaScript Generator]
D --> F[No Output - noEmitOnError]
E --> G[.js File]
E --> H[.map File - sourceMap]
G --> I[Node.js / Browser]
H --> J[Debugger]
style D fill:#ff6b6b,color:#fff
style F fill:#ff6b6b,color:#fff
style G fill:#51cf66,color:#fff
style H fill:#339af0,color:#fffThe TypeScript compiler does two things at once: type checking (verifying type correctness) and transpilation (converting TypeScript to JavaScript). Both happen in a single tsc step, but they’re conceptually separate — this is why you can use Babel for transpilation alone without type checking if needed.
ts-node: Run TypeScript Without a Build Step #
For development and scripting, there’s a far more practical tool than the tsc → node cycle: ts-node. This tool executes TypeScript directly without needing to produce JavaScript files first.
# Install ts-node as a dev dependency
npm install --save-dev ts-node
# Run a TypeScript file directly
npx ts-node src/index.ts
ts-node is very useful for:
- Running one-off scripts
- An interactive TypeScript REPL (
npx ts-nodewithout arguments) - Running test runners
- Quick debugging without a build
Don’t usets-nodein production.ts-nodedoes on-the-fly compilation, which is slower and consumes more memory than running already-compiled JavaScript. Usetscfor production builds, and run the JavaScript output fromdist/.
Configuring npm Scripts #
Add useful scripts to package.json to make the development workflow more convenient:
{
"name": "proyek-typescript",
"version": "1.0.0",
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
"clean": "rm -rf dist"
},
"devDependencies": {
"typescript": "^5.0.0",
"ts-node": "^10.9.0"
}
}
Now your development workflow becomes:
npm run dev # Run directly without build (development)
npm run build # Compile to dist/ (production prep)
npm start # Run the compiled output (production)
npm run build:watch # Watch mode for active development
Verifying Your Complete Setup #
Before starting to write serious code, verify that all components are properly installed using this checklist:
INSTALLATION CHECK:
□ node -v → shows the version (v18.x or v20.x)
□ npm -v → shows the version (v9.x or v10.x)
□ npx tsc --version → shows Version 5.x.x
□ tsconfig.json exists at the project root
□ "strict": true is enabled in tsconfig.json
WORKFLOW CHECK:
□ npm run dev successfully runs src/index.ts
□ npm run build produces files in dist/
□ npm start successfully runs dist/index.js
□ Deliberately create a type error → the compiler reports the error
□ Fix the error → the compiler compiles successfully
When Not to Use tsc Directly #
For large-scale projects, tsc is often integrated into more complex build tools. Here’s a quick guide:
Keep using tsc directly if:
✓ Simple to medium backend Node.js projects
✓ TypeScript libraries that will be published to npm
✓ Internal scripts and tooling
Consider a bundler (Webpack, Vite, esbuild) if:
✗ You're building frontend applications (React, Vue, etc.)
✗ The project needs code splitting and lazy loading
✗ You need bundle size optimization for the browser
✗ Integration with CSS modules, asset handling, etc.
Frameworks like Next.js, Nuxt, or NestJS handle TypeScript configuration internally — you don’t need to configure tsc manually when using those frameworks.
Summary #
- Node.js LTS is the foundation — install it via nodejs.org or use
nvmfor more flexible version management.- Install TypeScript as a
devDependency— don’t rely on a global installation for team projects because TypeScript versions can differ between machines and cause inconsistencies.tsconfig.jsonisn’t optional — this file controls the compiler’s entire behavior; enablestrict: truefrom day one to get the full benefit of TypeScript’s type system.noEmitOnError: true— an important option that ensures no JavaScript is produced while type errors exist, preventing accidental deployment of broken code.- Separate
src/anddist/— TypeScript source shouldn’t mix with JavaScript output; adddist/to.gitignore.ts-nodefor development,tscfor production — usets-nodefor direct execution during development, but always compile to JavaScript for production deployment.- Source maps are a must — with
sourceMap: true, the debugger points to the original TypeScript lines, not the compiler-generated JavaScript.- npm scripts simplify the workflow — define
build,dev,startinpackage.jsonso the whole team uses the same commands.