01 · Setup & First Program¶
🎥 Video walkthrough¶
Install Node.js¶
TypeScript compiles down to JavaScript, and that JavaScript runs on Node.js. Install a recent LTS release:
# macOS (Homebrew)
brew install node
# Ubuntu/Debian
sudo apt install nodejs npm
# Windows/any OS: installer from https://nodejs.org
Verify the install:
node runs JavaScript outside a browser; npm (Node Package Manager) installs
packages — including TypeScript itself.
Installing TypeScript¶
TypeScript is distributed as an npm package. Install it globally so the tsc
(TypeScript Compiler) command is available everywhere, or locally per-project
(the more common real-world choice):
# Global install -- fine for learning/experimenting
npm install -g typescript
# Per-project install -- the standard for real projects
mkdir my-ts-project && cd my-ts-project
npm init -y
npm install --save-dev typescript
Verify:
With a local install, run the compiler via npx (which finds the
project-local binary) instead of assuming it's on your global PATH:
Your first program¶
Create hello.ts:
// hello.ts
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("world"));
Compile it to plain JavaScript, then run the result with Node:
npx tsc hello.ts
# produces hello.js -- plain JavaScript, no types left
node hello.js
# Hello, world!
tsc type-checks your code and then erases all the types, emitting
ordinary JavaScript. Types exist only at compile time — they cost nothing at
runtime and leave no trace in the output.
Catching a type error¶
Change the call to pass a number instead of a string:
npx tsc hello.ts
# hello.ts:6:19 - error TS2345: Argument of type 'number' is not assignable
# to parameter of type 'string'.
tsc refuses to compile until the error is fixed (unless you explicitly tell
it not to) — this is the whole point of TypeScript: catching this class of bug
before the program ever runs, instead of discovering it at 2am in production.
tsconfig.json basics¶
Real projects don't call tsc with a filename directly — they use a
tsconfig.json that describes the whole project: which files to include, what
JavaScript to target, and how strict to be.
This generates a tsconfig.json with many options commented out. A sensible
starting point for a new project:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
| Option | Meaning |
|---|---|
target |
Which JavaScript version to emit (e.g. ES2022) |
module |
Module system for the output (commonjs for Node, esnext for bundlers) |
rootDir / outDir |
Where source lives, where compiled output goes |
strict |
Turns on the full set of strict type-checking flags (always keep this on — see Module 9) |
include |
Which files/folders tsc should compile |
With a tsconfig.json in place, just run tsc with no arguments from the
project root — it finds the config automatically:
mkdir src
mv hello.ts src/
npx tsc
# compiles src/hello.ts -> dist/hello.js, following tsconfig.json
Running TypeScript directly with ts-node¶
For quick scripts and learning, recompiling and then running a separate .js
file is friction you don't need. ts-node
compiles and runs TypeScript in one step:
ts-node is great for local development and this course's exercises. Compiled
tsc output is still what you'd deploy to production, since ts-node
recompiles on every run and carries extra startup overhead.
Choosing an editor¶
VS Code (free, from Microsoft) has first-class TypeScript support built in — inline error squiggles, autocomplete, and refactoring all work out of the box with zero configuration, since VS Code and TypeScript share the same underlying language service. Any other editor with a TypeScript plugin (WebStorm, Neovim + a language server) works too — pick one and move on, since the type system matters far more than the editor.
How It Actually Works¶
When you run tsc, three distinct phases happen in sequence, and understanding them explains almost every confusing error message you'll hit later.
1. Parsing. The compiler tokenizes your .ts source and builds an Abstract Syntax Tree (AST) — the same kind of tree a JavaScript-only parser would build, except TypeScript's parser also understands type annotations (: string), interfaces, generics, and other syntax that has no JavaScript equivalent. This is why a .ts file with a syntax error in a type position (like let x: ;) fails before type checking even starts — the parser can't build a tree at all.
2. Type checking. The checker walks the AST and, for every expression, computes a type and verifies it's compatible with however that expression is used — a variable's declared type, a function's parameter types, a returned value against a return type. This is the "type-checker" (checker.ts in the TypeScript source, one of the largest files in the compiler). Crucially, type checking is a static analysis pass only — it never runs your code. It infers and compares types by walking the tree, and its output is a list of diagnostics (errors/warnings), not a transformed program.
3. Emit. If you're transpiling (not just type-checking with --noEmit), a separate pass converts the AST to a plain-JavaScript AST by literally deleting every type-only construct — annotations, interfaces, generic parameters — and downleveling any modern syntax your target doesn't support. This is called type erasure: at runtime, TypeScript's types do not exist in any form. There's no runtime type-tag, no reflection API for your interfaces, nothing. A .d.ts declaration file is only ever consumed by the type checker in phase 2; it contributes zero bytes to phase 3's output.
The practical consequence: type errors and emit are independent. By default tsc still emits JavaScript even when type checking fails (you'll see the errors printed, but the .js file is written anyway) unless noEmitOnError is set — because "erase the types and emit JS" doesn't logically depend on "did the types check out."
🔀 See this in another language¶
Exercise¶
Set up a fresh project: run npm init -y, install typescript and
ts-node as dev dependencies, and generate a tsconfig.json with tsc
--init. Edit it to set "strict": true, "rootDir": "./src", and
"outDir": "./dist". Then write src/greeter.ts with a function that prints
a greeting for three different names, and run it directly with
npx ts-node src/greeter.ts. Finally compile it with npx tsc and run the
emitted JavaScript with node dist/greeter.js to confirm both paths work.