Install It In the Project, Not On the Machine
TypeScript is an npm package, so you need Node.js first. Every tutorial you will find offers you two commands: npm install -g typescript, which installs it once for your whole computer, and npm install --save-dev typescript, which installs it inside one project folder. Prefer the second, always.
The reason is version drift. TypeScript changes what it accepts between versions — new checks are added, inference gets sharper, occasionally something that compiled last year stops compiling. If the compiler lives on your machine rather than in the project, your laptop, your teammate's laptop and the build server can all be running different versions, and code that passes for you fails for them. A local install pins the version in package.json, so everyone runs the same compiler.
With a local install, the command is npx tsc rather than tsc. npx looks inside node_modules/.bin first and runs the project's own copy. The usual habit is to skip typing it altogether by putting the commands into npm scripts, so npm run typecheck works the same way on every machine.
# Start a project
mkdir ts-practice && cd ts-practice
npm init -y
# Install the compiler locally (note: --save-dev, not -g)
npm install --save-dev typescript
# Types for Node's own globals: process, __dirname, Buffer, fs, ...
npm install --save-dev @types/node
# Which version am I actually running?
npx tsc --version
# package.json
{
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"watch": "tsc --watch"
}
} - If
console.logorprocess.envis underlined as an unknown name, you are missing@types/node. Those are Node's globals, not JavaScript's, so TypeScript does not know about them until you install their descriptions.
Your First File, and What Compiling Produces
TypeScript source files end in .ts, or .tsx if they contain JSX. Write one, run the compiler over it, and you get a .js file next to it. Open that output file and read it — it is the single most useful five seconds in this lesson.
You will see your own code with the annotations removed. name: string has become name. The return type is gone. Nothing has been added, no helper library has been imported, and the file is the JavaScript you would have written by hand. That is what "types are erased" means in practice, and seeing it once removes a lot of mystery.
One thing that catches people out: by default tsc still emits JavaScript even when it reported errors. This is deliberate — the compiler assumes you may want to run the code anyway while you are mid-refactor — but it means a successful run of node dist/index.js is no proof that your types are clean. Read the compiler output, or turn on noEmitOnError.
// src/index.ts
function greet(name: string): string {
return `Namaste, ${name}!`;
}
console.log(greet("Ananya"));
// npx tsc -> produces dist/index.js:
//
// "use strict";
// function greet(name) {
// return `Namaste, ${name}!`;
// }
// console.log(greet("Ananya"));
//
// Same code. The types simply are not there any more.
// Run it: node dist/index.js tsconfig.json: the File That Defines the Project
A tsconfig.json in a folder means "this folder is a TypeScript project". Once it exists, running npx tsc with no file names compiles everything the config includes, using the options it lists. Generate a starting one with npx tsc --init; it arrives heavily commented, which is worth reading once.
Five options do most of the work. target is the JavaScript version to emit — set it to something modern such as ES2022 unless you must support very old browsers, because a low target makes the compiler rewrite clean modern syntax into bulky equivalents. module decides the module format of the output. rootDir and outDir keep source and output apart so that src/ stays clean and dist/ can be deleted at any time. And strict turns on the checks that make TypeScript worth using at all.
Turn strict on now, on day one, while the project is empty. It is the difference between a type system that catches missing null checks and one that mostly nods along. Enabling it later on a codebase that has grown without it typically produces hundreds of errors at once, which is how projects end up leaving it off forever.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"noEmitOnError": true,
"sourceMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
} skipLibCheck: truetells the compiler not to typecheck the.d.tsfiles insidenode_modules. Almost every real project turns it on: those files are not yours to fix, and a conflict between two libraries' declarations should not stop your build.sourceMap: truemeans the debugger and stack traces point at your.tsline numbers instead of the compiled output. Debugging without it is unpleasant.
Running TypeScript Without Compiling First
Edit, compile, run, repeat gets tiresome fast. There are three better loops, and which one you want depends on what you are building.
For learning and for scripts, use a runner that compiles in memory. tsx is the common choice today: npx tsx src/index.ts executes the file directly with no dist/ folder involved. Deno and Bun do the same natively. Be aware that these runners strip types rather than check them, so they will happily run a file that has type errors — which is fine while experimenting and dangerous as your only safety net.
For a long-running app, use tsc --watch in one terminal so the compiler recompiles on save and reports errors continuously. For anything with a front end, your bundler or framework already handles this: Vite, Next.js and Astro understand .ts out of the box. In all three cases, keep a separate npm run typecheck that runs tsc --noEmit, because that is the only step that actually enforces your types.
# Run a .ts file directly (no build folder)
npm install --save-dev tsx
npx tsx src/index.ts
# Recompile on every save
npx tsc --watch
# Check types across the whole project and emit nothing.
# This is the command that belongs in CI.
npx tsc --noEmit ts-nodeis the older tool you will still see in tutorials and existing projects. It works, but it needs more configuration to behave with ES modules. For a new project,tsxis the smoother choice.
Editor Setup, and the Mismatch That Wastes an Afternoon
VS Code has TypeScript support built in, so a fresh install already gives you red squiggles, autocomplete and go-to-definition. What is not obvious is that VS Code ships its own copy of TypeScript, and by default it uses that copy rather than the one in your project.
So if your project is pinned to one version and your editor bundles another, the editor and the terminal can disagree. You will see an error in npx tsc that your editor does not show, or the reverse, and you will lose an hour assuming your code is haunted. The fix is one setting: open a .ts file, run the command TypeScript: Select TypeScript Version, and choose Use Workspace Version.
The other habit worth forming immediately is to read the whole error message. TypeScript errors are often several sentences long and nest inside each other; the useful part is usually the last line, which names the two specific types that did not match. Beginners read the first line, panic, and add any.
- Set the editor to Use Workspace Version so the editor and the terminal run the same compiler
- Hover over any variable to see its inferred type — the fastest way to understand what TypeScript thinks is happening
- Add ESLint with
typescript-eslintonce the basics are comfortable; it catches things the compiler deliberately allows - Commit
tsconfig.jsonandpackage-lock.json; never commitdist/ornode_modules/ - Read TypeScript errors from the bottom up — the final line names the two types that actually conflicted
