TypeScript at a Glance
TypeScript is JavaScript plus static type checking. Types improve editor tooling and catch errors before runtime, then are normally erased from emitted JavaScript.
Current baseline: TypeScript 7.0.2.
macOS Setup & Installation
Use Node and npm on macOS. Prefer a local TypeScript installation for real projects.
Purpose / Commands / Notes
Node and npm provide the normal TypeScript installation workflow.
node -v
npm -v
which node
which npmIf you use nvm, activate the project Node version before installing dependencies.
Recommended for projects.
npm install -D typescript
npx tsc --versionThe compiler version is captured by package metadata and the lockfile.
Useful for quick experiments, but not the project baseline.
npm install -g typescript
tsc --versionReal repositories should still run their local TypeScript version.
No installation required.
Useful for syntax experiments, narrowing, compiler output, and type inference.
Create a TypeScript Project
Minimal project workflow for macOS and Node-based development.
Create a project and install TypeScript.
mkdir ts-demo
cd ts-demo
npm init -y
npm install -D typescript
npx tsc --initSeparate authored TypeScript from generated output.
mkdir src
touch src/index.tsExplicit annotation plus inference.
const project: string = "Sandbox";
const count = 3;
console.log(project, count);Check types without producing JavaScript.
npx tsc --noEmitCompile according to tsconfig.
npx tsc
node dist/index.jsRecompile when files change.
npx tsc --watchCompiler Command Reference
tsc is the TypeScript compiler. Prefer npx tsc so the project-local compiler is used.
Show the compiler version.
npx tsc --versionCreate tsconfig.json.
npx tsc --initCompile using the nearest tsconfig.
npx tscCheck types without writing JavaScript.
npx tsc --noEmitKeep the compiler running and rebuild when files change.
npx tsc --watchSelect another tsconfig.
npx tsc --project tsconfig.build.jsonPrint the final resolved configuration.
npx tsc --showConfigSee which files are in the compilation.
npx tsc --listFilesOnlyExplain why files were included.
npx tsc --explainFilesDiagnose import resolution.
npx tsc --traceResolutionDisplay the complete compiler option list.
npx tsc --help --allBuild referenced projects in dependency order.
npx tsc --buildtsconfig.json Quick Reference
tsconfig.json marks the project root and controls files, checking, module behavior, and JavaScript/declaration output.
Practical Node configuration. Install @types/node.
{
"compilerOptions": {
"target": "ES2025",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"types": ["node"],
"sourceMap": true,
"declaration": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
},
"include": ["src/**/*.ts"]
}Useful when Vite or another bundler handles JavaScript output.
{
"compilerOptions": {
"target": "ES2025",
"module": "Preserve",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true
},
"include": ["src"]
}Turns on the strict checking family.
"strict": trueControls the JavaScript syntax/runtime level TypeScript emits or assumes.
"target": "ES2025"Match these settings to the actual runtime or bundler.
"module": "NodeNext",
"moduleResolution": "NodeNext"Control source-tree structure and emitted output location.
"rootDir": "src",
"outDir": "dist"List global type environments your project needs.
"types": ["node"]Generate source maps so debuggers can map JavaScript back to TypeScript.
"sourceMap": trueEmit .d.ts files describing your public API.
"declaration": trueAdd undefined to uncertain indexed reads.
"noUncheckedIndexedAccess": trueDistinguish missing optional properties from explicitly supplied undefined values.
"exactOptionalPropertyTypes": trueEveryday Types & Syntax
Use annotations where they communicate intent. Let TypeScript infer obvious local values.
Objects, Interfaces & Type Aliases
Both interface and type can describe object shapes. Type aliases can also represent unions and other non-object types.
Concept / Syntax / Guidance
Named object contract.
interface User {
id: number;
name: string;
active?: boolean;
}Interfaces can extend other interfaces and participate in declaration merging.
Name an object, primitive union, tuple, mapped type, or other type expression.
type UserId = string | number;
type Point = {
x: number;
y: number;
};Especially useful for unions, intersections, and constructed types.
The property may be absent.
interface User {
nickname?: string;
}Optional does not mean every consumer can safely assume a value exists.
Prevent assignment through that typed reference.
interface User {
readonly id: number;
}This is compile-time protection, not automatic Object.freeze runtime behavior.
Unknown property names with a consistent value type.
interface Scores {
[name: string]: number;
}Useful for dictionary-like objects.
Require multiple contracts at once.
type Timestamped = {
createdAt: Date;
};
type SavedUser =
User & Timestamped;Good for clear composition; avoid unreadably deep intersections.
Check a value against a type while preserving useful inference.
const routes = {
home: "/",
users: "/users"
} satisfies Record<string, string>;Often preferable to a broad type assertion for configuration objects.
Functions
Type function boundaries where the contract matters. Let obvious implementation details infer naturally.
Basic function contract.
function add(a: number, b: number): number {
return a + b;
}The value may be undefined.
function greet(name?: string): string {
return `Hello ${name ?? "friend"}`;
}Provide a value when omitted.
function page(size = 20): number {
return size;
}Collect remaining arguments into an array.
function sum(...values: number[]): number {
return values.reduce((a, b) => a + b, 0);
}Describe a callable value.
type Formatter =
(value: number) => string;Preserve a relationship between input and output types.
function identity<T>(value: T): T {
return value;
}Require a minimum shape while keeping the specific input type.
function getId<T extends { id: string | number }>(
value: T
) {
return value.id;
}Unions & Narrowing
A union means a value can have several possible types. Narrowing proves which member is present before member-specific operations are used.
Model explicit alternatives.
let id: string | number;Narrow primitive-like values.
function format(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}Narrow array alternatives.
if (Array.isArray(value)) {
console.log(value.length);
}Check whether a property exists on an object-union member.
if ("meow" in pet) {
pet.meow();
} else {
pet.bark();
}Narrow class instances at runtime.
if (value instanceof Date) {
return value.toISOString();
}One of the best patterns for modeling application states.
type Result =
| { kind: "success"; data: User[] }
| { kind: "error"; message: string };
function render(result: Result) {
if (result.kind === "success") {
return result.data;
}
return result.message;
}Create a reusable custom type guard.
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value
);
}Generics
Generics let reusable code preserve relationships between types instead of collapsing everything into a broad base type.
Return the exact input type.
function identity<T>(value: T): T {
return value;
}Reuse one implementation across element types.
function first<T>(
items: readonly T[]
): T | undefined {
return items[0];
}Represent independent type positions.
function pair<K, V>(
key: K,
value: V
): [K, V] {
return [key, value];
}Make a key depend on the object type.
function get<T, K extends keyof T>(
obj: T,
key: K
): T[K] {
return obj[key];
}Parameterize reusable structures.
interface ApiResponse<T> {
data: T;
status: number;
}
type UserResponse =
ApiResponse<User[]>;Type Operators & Type Construction
Build new types from existing types and values.
Classes & OOP Syntax
TypeScript classes are JavaScript classes plus compile-time type features.
Typed fields and constructor.
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}public, protected, and private are TypeScript visibility tools.
class Account {
public name = "";
protected balance = 0;
private cache = new Map();
#secret = "runtime private";
}Prevent reassignment in typed code.
class Config {
constructor(
readonly apiUrl: string
) {}
}Check that a class instance conforms to an interface.
interface Printable {
print(): void;
}
class Report implements Printable {
print(): void {}
}Create a base contract that cannot itself be instantiated.
abstract class Shape {
abstract area(): number;
}Modules, Imports & Exports
Compiler module settings must agree with how Node or your bundler really loads JavaScript.
Standard ESM syntax.
// math.ts
export function add(
a: number,
b: number
) {
return a + b;
}
// app.ts
import { add } from "./math.js";One default export per module.
export default class App {}
import App from "./app.js";Make type-only dependencies explicit.
import type {
User
} from "./types.js";Runtime and type-only names together.
import {
save,
type SaveOptions
} from "./save.js";Load a module on demand.
const module =
await import("./feature.js");
module.run();Async, Promises & Fetch
TypeScript adds types to JavaScript asynchronous behavior; Promise mechanics remain JavaScript runtime behavior.
An async function returns a Promise.
async function loadUser(
id: string
): Promise<User> {
return getUser(id);
}Await a Promise inside an async/module context.
const user =
await loadUser("42");Run independent asynchronous work together.
const [user, posts] =
await Promise.all([
getUser(),
getPosts()
]);API data is runtime data. Treat it as unknown until validated.
const response =
await fetch("/api/users");
const raw: unknown =
await response.json();Thrown values are not guaranteed to be Error objects.
try {
await run();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
}
}DOM & Browser TypeScript
DOM declarations provide strongly typed elements, browser events, fetch, URL, storage, and related APIs.
Specify the expected element shape.
const input =
document.querySelector<HTMLInputElement>(
"#name"
);
if (input) {
console.log(input.value);
}Narrow element-specific event values.
input?.addEventListener(
"input",
event => {
const target =
event.currentTarget
as HTMLInputElement;
console.log(target.value);
}
);Typed browser form element.
const form =
document.querySelector<HTMLFormElement>(
"#profile"
);
form?.addEventListener(
"submit",
event => {
event.preventDefault();
}
);Stored values are strings and may be absent.
const theme =
localStorage.getItem("theme");
// string | nullTypeScript with Node.js
Add Node declaration packages for globals and built-in modules. Runtime execution and static checking are separate concerns.
Add Node globals and built-in module declarations.
npm install -D @types/nodeInclude Node global declarations explicitly.
{
"compilerOptions": {
"types": ["node"]
}
}Use the node: prefix for Node built-ins.
import {
readFile
} from "node:fs/promises";
const text =
await readFile(
"notes.txt",
"utf8"
);Environment values are strings or undefined.
const port =
Number(
process.env.PORT ?? 3000
);
console.log(
process.argv.slice(2)
);Modern Node can execute erasable TypeScript syntax, but that does not replace type checking.
node src/script.ts
npx tsc --noEmitConvenient development runner for Node TypeScript projects.
npm install -D tsx
npx tsx src/index.ts
npx tsx watch src/index.tsDeclaration Files & Third-Party Types
.d.ts files describe types without providing runtime implementation.
Install community declarations when a JavaScript package does not provide its own.
npm install -D @types/node
npm install -D @types/expressMany modern npm packages already contain declarations.
npm view some-package types
npm view some-package typingsdeclare module "legacy-lib" {
export function parse(
value: string
): unknown;
}{
"compilerOptions": {
"declaration": true
}
}npx tsc \
--declaration \
--emitDeclarationOnlyBuilt-in Utility Types
Transform existing types instead of duplicating related object definitions.
Modern Tooling & Build Integration
Many modern frameworks and bundlers transform TypeScript source separately while tsc provides static type checking.
Recommended baseline.
npm install -D typescript
npx tsc --versionVite handles source transformation. Keep type checking as a separate command.
npm create vite@latest
npx tsc --noEmitFast TypeScript execution for Node development.
npm install -D tsx
npx tsx watch src/index.tsLinting and type checking solve different problems.
npm install -D \
eslint \
typescript-eslintFormatting is separate from type correctness.
npm install -D prettier
npx prettier --write \
"src/**/*.{ts,tsx}"Useful for large workspaces and packages.
npx tsc --build
npx tsc --build --verboseUse only when testing upcoming compiler behavior.
npm install -D \
typescript@nextBest Implementation Practices
TypeScript is most useful when it makes boundaries, state, and relationships clearer instead of adding annotations everywhere.
Pin TypeScript in each repository and run it through package scripts or npx.
Fix or narrow difficult types instead of disabling strictness across the project.
Annotate public boundaries, external data, domain models, and non-obvious generic relationships. Let simple local values infer.
Discriminated unions are usually safer than several loosely related state booleans.
unknown requires narrowing before use. any simply disables useful checking.
value as User changes what the compiler believes. It does not change or validate the runtime value.
Make type-only dependencies explicit and prevent confusion between runtime and compile-time imports.
Use Node-aware module settings for Node and bundler-aware settings for bundler projects. Do not copy tsconfig options blindly.
Troubleshooting Fast Map
Check compiler version, effective configuration, included files, and module resolution before changing types blindly.
Official Documentation & Downloads
Use official TypeScript documentation for syntax, compiler configuration, modules, declarations, and version-specific changes.