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.
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: trueis non-negotiable. TS without strict is just JS with an extra build step.- Avoid
any; useunknown.unknownforces you to check before using —anyswitches 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
Productinterface above explains itself to the next developer.
Want something like this built for your business?