Chapter 2 of 24 · TypeScript for Beginners — Complete Free Tutorial
Setting Up TypeScript
The 24-chapter TypeScript for Beginners series keeps its toolchain deliberately small: Node.js, npm and one compiler. This chapter installs TypeScript in a fresh project, generates the tsconfig.json that controls every build, and sets up the compile-and-watch loop you will use for the rest of the series — including a first compiled example with its expected terminal output, and the one adjustment TypeScript needs when it meets Vite.
Installing TypeScript
TypeScript is an ordinary npm package, installed as a development dependency because it never ships to the browser — only the compiled JavaScript does. Create a playground folder and install it:
mkdir ts-playground
cd ts-playground
npm init -y
npm install -D typescript
npx tsc --version
Expected output:
Version 5.9.2
Two habits are worth forming now. The -D flag keeps typescript out of your production dependencies, and npx tsc runs the compiler installed inside this project. A global install feels convenient but quietly makes different projects compile with different versions — one of the nastier sources of “it works on my machine” bugs.
tsconfig.json: the control panel
npx tsc --init writes a tsconfig.json in the current folder. The generated file is long and mostly commented out, documenting every option the compiler understands; real projects edit only a handful of keys:
npx tsc --init
Expected output: no console output — a tsconfig.json file simply appears next to package.json. Trim it to this, which is the configuration this series assumes:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
| Option | What it does | Why we set it |
|---|---|---|
target | The JavaScript version tsc emits | ES2022 keeps modern syntax such as optional chaining as-is |
module | The module format of the output (import / export) | ESNext for bundlers; use NodeNext for plain Node projects |
strict | Switches on the whole family of safety checks, including strictNullChecks and noImplicitAny | Every example in this series compiles with strict: true; it gets a deep dive in chapter 20 |
outDir | Folder that receives the emitted JavaScript | dist keeps compiled files away from the sources you edit |
rootDir | Root of the sources, mirrored inside outDir | src/main.ts becomes dist/main.js, never a stray file at the top |
include | Which files tsc considers at all | ["src"] stops the compiler wandering into build output and old experiments |
The compile and watch workflow
With a config in place, a bare npx tsc compiles every file matched by include. A successful build prints nothing at all — silence is success:
mkdir src
# create src/main.ts first
npx tsc
Expected output: no messages, and a fresh dist/ folder containing main.js. Typing that command after every save gets old quickly, so pass --watch (or -w) and let the compiler follow you around:
npx tsc --watch
Expected output:
[09:15:02] Starting compilation in watch mode...
[09:15:02] Found 0 errors. Watching for file changes.
Every save triggers a recompile; errors print into the same window and the process stays alive, so one broken line never ends your session. When you only want the verdict — in a script, a pre-commit hook or continuous integration — use npx tsc --noEmit to type-check without writing any files.
Your first compiled example
Create src/greeting.ts with a tiny typed program:
const price: number = 49.5;
const taxRate: number = 0.2;
const total: number = price * (1 + taxRate);
console.log(`Total: ${total.toFixed(2)}`);
Compile it, then run the emitted JavaScript with Node:
npx tsc
node dist/greeting.js
Expected output:
Total: 59.40
The file landed in dist/ because of outDir, and Node happily ran the result — proof that .js is all a runtime ever needs. Now break the build on purpose by changing only the annotation:
const total: string = price * (1 + taxRate);
Expected output:
error TS2322: Type 'number' is not assignable to type 'string'.
The maths is fine; the promise is not. Restore : number and tsc goes silent again. One detail to know: type errors do not stop JavaScript being written by default, so add "noEmitOnError": true to compilerOptions if you never want broken output reaching dist/.
TypeScript with Vite
Most modern projects do not hand-run tsc — a build tool drives everything. Vite understands TypeScript out of the box, and its starter templates come in plain-JS and TS flavours:
npm create vite@latest ts-vite-app -- --template vanilla-ts
cd ts-vite-app
npm install
npm run dev
Expected output:
VITE v6.x.x ready in 400 ms
➜ Local: http://localhost:5173/
Three facts make the Vite setup predictable:
- Dev mode is transpile-only — Vite uses esbuild to strip types instantly, without checking them, so red squiggles come from your editor rather than the dev server.
- The build script checks first — the generated
buildscript runstscbefore bundling (typicallytsc -b && vite build), which is where type errors surface. - One config each —
tsconfig.jsonsits besidevite.config.ts;moduleResolution: "bundler"matches how Vite resolves imports. Thereact-tstemplate uses the same arrangement if you continue into Setting Up React with Vite.
How this series uses the project
Keep one folder open for all 24 chapters: a src/ source directory, a dist/ build directory, and one file per chapter — src/ch03-annotations.ts, src/ch04-arrays.ts and so on. Leave tsc --watch running in a second terminal pane so every snippet you type is checked the moment you save it. Typing the examples yourself, errors included, is what turns reading into skill.
Common mistakes
- Running a stale global compiler — a bare
tscmay resolve to an old global install; usenpx tscso the project's own version runs. - Compiling one file by name —
tsc src/main.tsmakes the compiler ignoretsconfig.jsonentirely, sooutDir,includeandstrictsilently stop applying. - Expecting type errors from
npm run dev— Vite's dev server transpiles without checking; rely on your editor, or run the build script. - Editing files in
dist/— that folder is regenerated on every compile; change the sources insrc/instead. - Running
tscwith an emptyinclude— compiling before any file exists undersrc/yieldsNo inputs were found in config file 'tsconfig.json'; create the file first.
Practice checklist
- Install TypeScript locally and confirm
npx tsc --versionreports a 5.x release. - Generate
tsconfig.jsonwithtsc --init, trim it to the six options above, and explain each one aloud. - Compile the tax example, run it from
dist/, and confirmTotal: 59.40. - Reintroduce the string annotation, read the TS2322 error, then start
tsc --watchand watch errors appear and disappear as you edit. - Scaffold the
vanilla-tsVite template and verify that a deliberate type error shows in the editor but not in the running dev server.
What's next?
The compiler is installed, the config is understood and the watch loop is running — now write real annotations in Basic Types and Annotations.
Frequently Asked Questions
TypeScript is a strict superset of JavaScript that adds a static type system. You annotate the promises your data makes, the compiler checks every one before the code runs, then erases the types and emits ordinary JavaScript. You catch wrong arguments, typoed properties and forgotten null checks while you are still typing, and refactoring plus editor autocomplete both improve sharply.
You should. This series assumes you can already read and write JavaScript: functions, arrays and objects, closures, modules and async/await. TypeScript audits that knowledge rather than replacing it, and types cannot rescue code you do not understand. If those fundamentals feel shaky, work through the JavaScript for Beginners series on this site first, and this course will go much faster.
No. TypeScript is compiled, not interpreted: tsc parses your file, type-checks it, then erases every annotation and writes ordinary JavaScript. The browser and Node.js only ever run that emitted .js, so runtime behaviour, performance and debugging are unchanged. It also never validates API data at runtime, so the series teaches you to distrust every boundary that leaves the compiler.
If you already know JavaScript, TypeScript is a modest step rather than a new language: the syntax is the same plus a colon. Most beginners read their first red squiggle within days and feel comfortable in two to four weeks of regular practice. The curve steepens later around generics and advanced types, which is where this 24-chapter series deliberately spends its second half.
Both name the same object shapes, so either works for a simple contract. The differences decide the rest: only interfaces merge when declared twice, while only type aliases can express unions, primitives, tuples and mapped types. The series default is interface for props and exported shapes other files consume, and type for everything else. Pick one style per project and stay consistent.
strict: true is a bundle of safety switches, not a single behaviour. It turns on noImplicitAny, so every parameter must be inferrable, strictNullChecks, so null and undefined are their own types you must narrow, strictFunctionTypes, noImplicitThis, strictPropertyInitialization, and useUnknownInCatchVariables, which types catch values as unknown. Every example in this series compiles with it on.
any accepts anything and switches the compiler off, so one any spreads through inference into everything derived from it and you are back to dynamic typing with a .ts extension. unknown accepts anything too, but forbids property access until you narrow it with a typeof or instanceof check. The series pushes you toward unknown because it keeps the check you installed TypeScript to get.
Yes to both. Vite understands TypeScript natively: scaffold the vanilla-ts or react-ts template and a tsconfig.json is waiting for you, with moduleResolution set for the bundler. JSX belongs in .tsx files, props get an interface, and hooks infer or accept explicit type arguments. The series covers typed React in depth, from props, state and events to refs, context and fetch effects.
Enums still compile and work fine in TypeScript 5.x, but they are no longer the default recommendation. A union of string literals such as "light" | "dark" needs no runtime object, matches JSON directly and plays well with single-file transpilers like Vite's esbuild. Keep enum for existing codebases that already use it, avoid const enum, and prefer literal unions in new code.
Adopt it incrementally instead of rewriting. Install typescript, add a tsconfig.json with allowJs and noEmit so .js and .ts coexist while your bundler keeps building, then check individual files with a // @ts-check comment. Rename leaf modules to .ts one at a time, fix implicit any errors first, and gate every pull request with tsc --noEmit so untyped code cannot sneak back in.