Docs menu

Learning Logs · · 3 min read

Learning TypeScript: from installation to generics

A mentoring-style TypeScript tutorial: installation + a correct tsconfig, the basic types that cover 90% of needs, generics with real examples from this site, and gradual migration from JavaScript.

TypeScriptNode.jstsconfig

TypeScript is JavaScript + a type system. Once it clicks, it feels like having a reviewer check your code before it runs. This tutorial is the path I actually took — from installation to the patterns that get real use in projects (including this site, which is 100% TypeScript).

1. Installation

Prerequisite: Node.js 20+. Then:

npm install -D typescript
npx tsc --init          # creates tsconfig.json

The minimum config I recommend for new projects:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,            // turn on from day one — this is the whole point
    "noEmit": true             // let the bundler (Vite/esbuild) do the transpiling
  },
  "include": ["src"]
}

Type-check anytime with npx tsc --noEmit — in my projects this is always the npm run typecheck script.

2. The basic types that cover 90% of needs

// Explicit annotations only where TS can't infer
let name: string = "Alif";        // ❌ redundant — TS already knows from the value
let name2 = "Alif";               // ✅ inference does the work

// Interfaces for object shapes
interface Product {
  id: number;
  name: string;
  price: number;
  discount?: number;              // optional
}

// Union types — a value that may only be one of these
type Status = "draft" | "published";

function publish(p: Product, status: Status) {
  if (status === "published") { /* TS knows status is exactly "published" here */ }
}

3. Generics — understand once, addicted forever

Generics = functions/types that “defer” their type decision to the caller:

function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

const p = first([{ id: 1, name: "Coffee", price: 25000 }]);
// p is typed Product | undefined — no casts, no any

A real example from this site — the i18n dictionary resolver that turns translation nodes into strings while preserving the object shape:

type Resolved<T> = T extends { nodeType: string } ? string
  : T extends readonly (infer U)[] ? Resolved<U>[]
  : T extends object ? { [K in keyof T]: Resolved<T[K]> } : T;

4. Gradual migration from JavaScript

No rewrite needed. Set "allowJs": true in tsconfig.json, rename files from .js to .ts one at a time, and fix the errors that surface. Prioritize the files that change most often — that’s where types save you the most.

Tips from experience

  • strict: true is non-negotiable. TS without strict is just JS with an extra build step.
  • Avoid any; use unknown. unknown forces you to check before using — any switches off everything TS gives you.
  • Let inference work. Over-annotation makes code noisy; annotate function parameters and public API return values, let TS infer the rest.
  • Types are documentation that never goes stale — the Product interface above explains itself to the next developer.

Want something like this built for your business?