Command Palette

Search for a command to run...

Hectal
PHASE 0Beginner ~21 min· topic 4 of 4

Topic 0.4

Your First TypeScript Project

In one line

Install TypeScript in a folder with npm, create a tsconfig.json with tsc --init, write a .ts file, then check it with tsc and run it with Node, which can run TypeScript files directly by stripping the types.

Think of it like this

Setting up a new kitchen. You bring in the cooker (Node.js), buy one special tool for this kitchen only (TypeScript, installed in the project), pin the house rules on the wall (tsconfig.json), then start cooking (write .ts files) and tasting as you go (tsc checks). A TypeScript project is a folder with those four things.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Node.js
A program that runs JavaScript (and now TypeScript, by stripping types) outside the browser.
npm
The package manager that comes with Node.js. It downloads packages and records them in package.json.
Package
A bundle of ready-made code published to the npm registry, like typescript.
Dev dependency
A package needed while you build the project (like TypeScript) but not by the finished program. Installed with -D.
npx
Runs a command from the project's own installed packages, e.g. npx tsc.
tsconfig.json
The project's TypeScript settings file. Its presence marks the folder as a TypeScript project.
Watch mode
tsc --watch: keep running and re-check every time a file is saved.
Type stripping
Deleting type syntax to get runnable JavaScript, without checking anything. Node.js does this for .ts files.

Step by step

01Make a project folder

Create an empty folder and open a terminal in it. npm init -y writes a package.json with default values (-y answers yes to every question).

terminal
$ mkdir hello-ts && cd hello-ts && npm init -y
package.json now exists, with "type": "commonjs" (Node's older module format; Topic 11.2 explains the choice).

02Install TypeScript for this project

-D saves it as a dev dependency. npm also downloads the small native compiler binary for your operating system.

terminal
$ npm install -D typescript
── expected output ──
added 21 packages, and audited 22 packages in 3s
 
found 0 vulnerabilities

03Create tsconfig.json

Check the version, then let TypeScript write a starting configuration.

terminal
$ npx tsc --version && npx tsc --init
── expected output ──
Version 7.0.2
 
Created a new tsconfig.json
 
You can learn more at https://aka.ms/tsconfig

04What --init wrote

This is the real file (TypeScript 7.0.2), with its comments. Strict checks are on, plus two extra-safe options, noUncheckedIndexedAccess (Topic 1.4) and exactOptionalPropertyTypes (Topic 1.5). This course's live editor uses the same set.

tsconfig.jsonwhole filejson
{
  // Visit https://aka.ms/tsconfig to read more about this file
  "compilerOptions": {
    // File Layout
    // "rootDir": "./src",
    // "outDir": "./dist",

    // Environment Settings
    // See also https://aka.ms/tsconfig/module
    "module": "nodenext",
    "target": "esnext",
    "types": [],
    // For nodejs:
    // "lib": ["esnext"],
    // "types": ["node"],
    // and npm install -D @types/node

    // Other Outputs
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,

    // Stricter Typechecking Options
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,

    // Style Options
    // "noImplicitReturns": true,
    // "noImplicitOverride": true,
    // "noUnusedLocals": true,
    // "noUnusedParameters": true,
    // "noFallthroughCasesInSwitch": true,
    // "noPropertyAccessFromIndexSignature": true,

    // Recommended Options
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
  }
}

05Write, check, run

Create index.ts. Run npx tsc: no output means no errors. It wrote index.js, plus index.js.map (source map), index.d.ts and index.d.ts.map (type declarations for other code to use, Topic 11.3), because the config turned those on.

Run the JavaScript with node index.js, or skip the build and run the TypeScript with node index.ts.

index.tswhole filets
function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("Asha"));
terminal
$ npx tsc && node index.js && node index.ts
── expected output ──
Hello, Asha!
Hello, Asha!

06See an error in the terminal

Change the call to greet(42). tsc --noEmit --pretty false prints the same one-line form as this course's Types panel; the exit code is 1. Node still runs the file, because it never checks types.

terminal
$ npx tsc --noEmit --pretty false; node index.ts
── expected output ──
index.ts(5,19): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
Hello, 42!

07Keep it checking: watch mode

--watch clears the screen and re-checks on every save. Leave it running in a second terminal while you work. Stop it with Ctrl+C.

terminal
$ npx tsc --watch --noEmit
── expected output ──
12:59:13 PM - Starting compilation in watch mode...
 
index.ts(5,19): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
12:59:13 PM - Found 1 error. Watching for file changes.
Keep it checking: watch modediagram
Rendering diagram…

Try it yourself

  1. 1

    Ask for more types

    In the first example, add a line const loud = message.toUpperCase(); and a // ^? line under it with the caret under loud. Predict the answer, then Run.

  2. 2

    Break the return type

    In the second example, change the return line to return name.length;. The function promised a string and now returns a number: read where the error appears (the return, not the call).

  3. 3

    On your computer

    Follow the walkthrough in a real folder. Then add enum Color { Red, Green } and console.log(Color.Green); to index.ts. npx tsc && node index.js prints 1, but node index.ts fails: enums need generated code, which Node's type stripping doesn't do.

Code & diagrams

Your first program in the live editor

Same program as the terminal walkthrough. The ^? lines ask for the types: greet returns string, inferred from the template string, so message is a string too.

Console

(nothing was printed)

Expected console output

Hello, Asha!
The same error you saw in the terminal

Exactly tsc's line from the walkthrough. Like node index.ts, the editor still runs the code.

Console

(nothing was printed)

Expected console output

Hello, 42!

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Run an enum with Node's type stripping

Put enum Color { Red, Green } and console.log(Color.Green); in enum.ts and run node enum.ts (Node 24.11).

terminal
$ node enum.ts
── what you'll see ──
/path/to/hello-ts/enum.ts:1
enum Color { Red, Green }
^^^^^^^^^^^^^^^^^^^^^^^^^
console.log(Color.Green);
 
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
at parseTypeScript (node:internal/modules/typescript:68:40)
... (more stack lines)

Break #2

Name a file when a tsconfig.json exists

Run npx tsc index.ts in the project folder.

terminal
$ npx tsc index.ts
── what you'll see ──
error TS5112: tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error.

Myth vs fact

Myth

Install TypeScript globally once with npm install -g typescript.

Fact

Install it in each project (-D) so every person and the CI use the same version; run it with npx tsc. A global install drifts out of sync.

Myth

node index.ts checks my types.

Fact

Node only strips types and runs what's left. Errors are invisible to it. Run tsc --noEmit (or watch mode) to check.

Myth

You must compile before you can run TypeScript.

Fact

Modern Node runs .ts files directly, and bundlers like Vite strip types on the fly. Compiling with tsc is one option; checking with tsc is the part you can't skip.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    module: nodenext makes TypeScript follow Node's rules exactly: whether a file is ESM or CommonJS depends on package.json "type" and the file extension (.mts/.cts), and relative ESM imports need file extensions. That's why --init picks it for Node projects, and why browser/bundler projects use bundler instead (Topic 11.2).

  • ▸

    Node's type stripping replaces type syntax with whitespace, so line and column numbers in stack traces match your source with no source maps. It ignores tsconfig.json entirely, and type-only imports must use import type (otherwise Node tries to import a value that doesn't exist); verbatimModuleSyntax in the tsconfig makes TypeScript enforce that.

  • ▸

    For CI, tsc --noEmit exits non-zero on any error and is enough on its own; projects with references use tsc --build --noEmit (Topic 12.4). With 7.0, --checkers can be raised on big CI machines for more speed.

Remember this

  1. 1

    The problem this solves: Starting TypeScript with a wrong setup, such as a global install or a different version on each machine, gives results that differ between people. A project-local setup makes everyone's checks match. You need Node.js, the program that runs JavaScript outside the browser and comes with npm, the tool that downloads packages (ready-made code) from the npm registry. Check both with node --version and npm --version. The outputs in this topic are real, made with Node 24.11 and TypeScript 7.0.2.

  2. 2

    TypeScript is installed per project, as a dev dependency (a package needed while developing, not by the finished program): npm install -D typescript. It goes into the project's node_modules folder and is recorded in package.json, so everyone on the team gets the same version. You run it with **npx tsc**: npx runs a command from the project's own node_modules.

  3. 3

    **npx tsc --init writes a tsconfig.json**, the project's settings file. Its presence tells tsc "this folder is a TypeScript project", which files belong to it, and how strict to be. TypeScript 7's --init writes modern, strict settings with comments (shown below). Phase 12 goes through every option; for now, know that strict is on and module: nodenext means "follow Node's own rules for modules".

  4. 4

    With a tsconfig.json, running **npx tsc** with no file names checks every .ts file in the folder and writes .js files next to them. **npx tsc --noEmit only checks. npx tsc --watch** keeps running and re-checks every time you save. Note: with a tsconfig.json present, tsc index.ts (naming a file) is an error, TS5112; plain tsc is the normal way.

  5. 5

    **Node.js can run .ts files directly**: node index.ts. It doesn't check types; it strips them (deletes the type syntax and runs what's left), so it's fast. According to Node's docs this is on by default since v22.18.0 and v23.6.0, without an experimental warning since v22.18.0 and v24.3.0, and stable since v24.12.0 and v25.2.0. Because it only deletes, syntax that needs to *generate* JavaScript (enum, namespaces with values, parameter properties) fails in Node with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. The usual workflow: Node or a bundler to run, tsc --noEmit to check (Topic 12.5).

  6. 6

    Your editor is the other half. VS Code understands TypeScript out of the box and shows errors as red squiggles while you type; hovering a name shows its type (that's what // ^? imitates here). For TypeScript 7's faster language server, VS Code has the "TypeScript 7" extension (TypeScriptTeam.native-preview). Other editors (WebStorm, Zed, Neovim) use the same language server.

  7. 7

    How this course's live editor works: each example is one file, index.ts, with the same strict settings as a tsc --init project (adjusted for the browser: module: esnext with moduleResolution: bundler). The Types panel shows exactly what tsc --pretty false prints, then the answers to your // ^? queries. Then the JavaScript runs and the Console shows what it prints. Edit freely: Run (or Ctrl+Enter) re-checks; the reset button brings the original back.

Explain it without notes

01

Walk through creating a TypeScript project from an empty folder.

02

What's the difference between tsc, tsc --noEmit and node index.ts?

03

Why is TypeScript installed per project instead of globally?

Practice

01

Your teammate's script runs tsc src/index.ts and now fails with TS5112. Rewrite the command so it checks the whole project with its tsconfig and writes nothing.

02

Write the scripts section of a package.json with check (type-check only), watch (re-check on save) and start (run index.ts with Node).

Trade-offs

  • ↔

    Running .ts directly with Node (or a bundler) is fastest to start but checks nothing; tsc checks but is a separate step. Most projects do both: strip to run, tsc --noEmit to check.

  • ↔

    tsc --init's strict settings catch more bugs but feel noisy at first. Learning with them on (as this course does) is much easier than switching them on in a big codebase later.

Done when you can

  • I can create a TypeScript project: npm init, npm install -D typescript, npx tsc --init.

  • I can check a project with npx tsc --noEmit and keep it checking with --watch.

  • I can run a .ts file with Node and explain what type stripping can't run.

  • I can read the same error in the terminal and in this course's Types panel.

  • I can use the live editor: edit, Run, read the Types panel, use // ^?.