0% found this document useful (0 votes)
2 views12 pages

TypeScript Zero to FullStack

This document is a comprehensive learning guide for TypeScript, covering its fundamentals, type system features, and full-stack development with React and Node.js. It is structured into three parts: the core language, the type system, and practical applications in full-stack development. The guide emphasizes the importance of TypeScript's type system in preventing runtime errors and enhancing code quality.

Uploaded by

mohanraj89it
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views12 pages

TypeScript Zero to FullStack

This document is a comprehensive learning guide for TypeScript, covering its fundamentals, type system features, and full-stack development with React and Node.js. It is structured into three parts: the core language, the type system, and practical applications in full-stack development. The guide emphasizes the importance of TypeScript's type system in preventing runtime errors and enhancing code quality.

Uploaded by

mohanraj89it
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

TypeScript from Zero to Full-Stack

A complete personal learning guide — fundamentals through React & Node in


production
Prepared for Mohanraj R
Format: Theory → Syntax → Code → Explanation → Examples
How this guide is organized
You already know JavaScript and Python, so this moves fast on programming fundamentals and focuses
on what's actually new: the type system. Part 1 builds the core language. Part 2 covers the type-system
features that make TypeScript worth using. Part 3 is what a full-stack developer role actually needs —
TypeScript wired into React and Node/Express.

PART 1 — Core Language


1. What TypeScript is and why it exists
THEORY
TypeScript is JavaScript plus a type system, built by Microsoft. Browsers and Node don't understand
TypeScript directly — a compiler (tsc) converts .ts files into plain .js before they run. Every valid
JavaScript file is already valid TypeScript; you're adding safety on top, not replacing the language.
The type system exists to catch a class of bugs at compile time — before the code ever runs — instead of
discovering them in production. If you pass a string where a function expects a number, TypeScript tells
you immediately, in your editor, instead of your app breaking for a user.
EXAMPLE — the problem TS solves
// plain JavaScript — this runs, and fails at runtime
function getTotal(price, quantity) {
return price * quantity;
}
getTotal("10", "abc"); // NaN, discovered only when it breaks something

// TypeScript — caught immediately, before you even run it


function getTotal(price: number, quantity: number): number {
return price * quantity;
}
getTotal("10", "abc"); // ❌ compiler error: Argument of type 'string' is not
assignable to type 'number'

2. Setting up a TypeScript project


SYNTAX
npm install -D typescript
npx tsc --init # creates [Link]
npx tsc # compiles all .ts files per [Link]
npx tsc --watch # recompiles automatically on save

# faster dev loop — runs .ts directly without a separate compile step
npm install -D ts-node
npx ts-node src/[Link]

EXPLANATION
[Link] is the control panel for the whole project — it decides which files are included, how strict
type-checking is, and what JavaScript version gets output. You'll edit it constantly on real projects, so
know these fields:
Field What it controls
strict Turns on all strict type-checking at once. Always keep this true on real
projects.
target Which JS version the compiler outputs (e.g. ES2020, ES2022)
module Module system used in output (commonjs for Node, esnext for
bundlers)
outDir / rootDir Where compiled JS goes / where source .ts lives
esModuleInterop Fixes import friction between CommonJS and ES module packages
skipLibCheck Skips type-checking inside node_modules — speeds up builds

3. Basic types
SYNTAX
let age: number = 25;
let name: string = "Mohanraj";
let isActive: boolean = true;
let scores: number[] = [90, 85, 76];
let scoresAlt: Array<number> = [90, 85, 76];
let anything: any = "avoid this — turns off type checking entirely";
let notSureYet: unknown = fetchSomeData(); // safer alternative to any

EXPLANATION
Most basic types mirror JavaScript's runtime types directly. The two you'll be asked about in interviews:
any disables type checking for that variable completely (defeats the point of TypeScript — avoid it),
while unknown also accepts anything but forces you to narrow the type (check what it actually is) before
you can use it. Prefer unknown whenever you're not sure what you're getting, like an API response.
EXAMPLE
let data: unknown = [Link](responseText);

if (typeof data === "string") {


[Link]([Link]()); // ✅ TS now knows it's a string here
}
// [Link]() directly would ❌ error — TS won't assume

4. Type inference
THEORY
You don't have to annotate every single variable. TypeScript infers the type from the assigned value
automatically. Annotate function parameters and return types explicitly (TS can't guess those from
nothing), but let inference handle simple local variables.
let city = "Chennai"; // inferred as string, no annotation needed
let count = 5; // inferred as number
// city = 10; ❌ error — TS locked the type in from the first
assignment
5. Interfaces and type aliases — shaping objects
SYNTAX
interface Student {
name: string;
age: number;
email?: string; // optional property
readonly id: number; // can't be reassigned after creation
}

type StudentAlt = {
name: string;
age: number;
email?: string;
};

const s1: Student = { name: "Mo", age: 25, id: 101 };

EXPLANATION
interface and type do the same basic job here — describing the shape of an object. Two practical
differences: interfaces can be reopened and extended later (declaration merging), and only interface
uses the word extends for inheritance; type is more flexible for unions, tuples, and complex
compositions covered in Part 2.
Use interface when... Use type when...
Defining the shape of an Defining a union, tuple, or a type built from other types
object or class contract
The shape might be You want one flexible alias, not meant to be reopened
extended by others later
(libraries, plugins)

EXAMPLE — extending
interface Person {
name: string;
}
interface Employee extends Person {
employeeId: number;
}
const e: Employee = { name: "Mo", employeeId: 42 };

6. Functions
SYNTAX
function add(a: number, b: number): number {
return a + b;
}

// optional and default parameters


function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}`;
}

// arrow function with typed params


const multiply = (a: number, b: number): number => a * b;
// function that returns nothing
function logMessage(msg: string): void {
[Link](msg);
}

EXPLANATION
Annotate parameters always. Annotate return types on exported/public functions even though TS can
often infer them — it documents intent and catches accidental return-type drift when you edit the
function body later.

7. Arrays, tuples, and enums


SYNTAX
let ids: number[] = [1, 2, 3];
let names: string[] = ["Mo", "Kavi"];

// tuple — fixed length, fixed types per position


let point: [number, number] = [10, 20];
let entry: [string, number] = ["age", 25];

// enum — named set of constants


enum Role {
Admin,
Editor,
Viewer,
}
let myRole: Role = [Link]; // 0

enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
}

EXPLANATION
A tuple is an array where position matters and each slot has its own type — useState in React actually
returns a tuple ([value, setValue]), which is why destructuring it works so cleanly with correct types.
Enums give named constants instead of magic strings/numbers scattered through code; string enums
(like Status above) are usually preferred in real projects because they're readable in logs and debugger
output.

8. Classes
SYNTAX
class Animal {
private name: string;
protected sound: string = "...";

constructor(name: string) {
[Link] = name;
}

makeSound(): void {
[Link](`${[Link]} says ${[Link]}`);
}
}

class Dog extends Animal {


protected sound = "Woof";
}

const d = new Dog("Rex");


[Link](); // "Rex says Woof"

EXPLANATION
public (default), private, and protected control visibility — private members are only usable inside the
class itself; protected is usable in the class and its subclasses; public is open everywhere. This matters in
backend code (Express services, repository classes) where you want to hide internal implementation
details from the rest of the app.
EXAMPLE — shorthand constructor
class User {
constructor(
public name: string,
private password: string
) {}
}
// equivalent to declaring both fields and assigning them in the body —
// this shorthand is used constantly in real backend code

PART 2 — The Type System (what actually makes TypeScript


worth it)
9. Union types, intersection types, and narrowing
SYNTAX
type ID = string | number; // union — could be either
let userId: ID = "abc123";
userId = 42; // also valid

type Employee = { name: string } & { employeeId: number }; // intersection — has


both

THEORY — narrowing
When a variable's type is a union, TypeScript won't let you use methods that don't exist on every
possibility until you narrow it down — proving to the compiler which branch you're in.
EXAMPLE
function printId(id: string | number) {
if (typeof id === "string") {
[Link]([Link]()); // ✅ safe — TS knows it's a string here
} else {
[Link]([Link](2)); // ✅ safe — TS knows it's a number here
}
}

EXAMPLE — discriminated unions (very common in real apps)


type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: string[] };
type ErrorState = { status: "error"; message: string };
type State = LoadingState | SuccessState | ErrorState;

function render(state: State) {


switch ([Link]) {
case "loading": return "Loading...";
case "success": return [Link](", "); // TS knows .data exists here
case "error": return [Link]; // TS knows .message exists
here
}
}

This pattern — a shared status field that discriminates which shape you're dealing with — is exactly how
you'll model API/UI states (loading/success/error) in a real React app.

10. Generics
THEORY
A generic is a placeholder type — you write a function or type once, and it works correctly for whatever
type gets passed in later, without losing type safety (unlike any, which throws safety away entirely).
SYNTAX
function identity<T>(value: T): T {
return value;
}
identity<string>("hello"); // T = string
identity(42); // T inferred as number, no need to specify

interface ApiResponse<T> {
data: T;
status: number;
}
const res: ApiResponse<string[]> = { data: ["a", "b"], status: 200 };

EXAMPLE — generic function with a constraint


function getFirst<T extends { length: number }>(arr: T): number {
return [Link];
}
getFirst([1, 2, 3]); // ✅ arrays have .length
getFirst("hello"); // ✅ strings have .length too

extends here means "T must at least have a length property," not class inheritance. You'll see generics
everywhere: React's useState<T>(), Express's Request<Params, ResBody, ReqBody>, Promise<T>, arrays.

11. Utility types — built-in type transformers


SYNTAX
interface User {
id: number;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, "password">; // all fields except password
type UserPreview = Pick<User, "id" | "name">; // only id and name
type PartialUser = Partial<User>; // every field optional (great
for update endpoints)
type ReadonlyUser = Readonly<User>; // no field can be reassigned
type UserRecord = Record<string, User>; // dictionary keyed by string,
value User

EXPLANATION
Utility Real use case
Partial<T> PATCH/update endpoints where any subset of fields might be sent
Pick<T, K> API response that only exposes select fields
Omit<T, K> Same object shape minus a sensitive field (like password)
Record<K, V> Lookup maps / dictionaries with typed keys and values
Required<T> Force every optional field to become mandatory
ReturnType<F> Extract the return type of a function without repeating it

12. Type guards and assertions


SYNTAX
// custom type guard function
function isString(value: unknown): value is string {
return typeof value === "string";
}

// type assertion — "trust me, I know the type"


const input = [Link]("email") as HTMLInputElement;
[Link]([Link]);

EXPLANATION
A custom type guard (the value is string return type) lets you write your own reusable narrowing logic,
useful when checking API responses. Type assertions (as) don't perform any runtime check — they just
tell the compiler to trust you. Use them sparingly, only when you genuinely know more than the
compiler can infer (e.g., you know a DOM element by ID is definitely an input).

13. Modules
SYNTAX
// [Link]
export function add(a: number, b: number): number {
return a + b;
}
export default class Calculator { /* ... */ }

// [Link]
import Calculator, { add } from "./mathUtils";
import type { User } from "./types"; // type-only import — erased at compile
time

EXPLANATION
import type explicitly marks an import as type-only — it disappears entirely from the compiled
JavaScript, keeping bundles smaller and making the intent clear ("I only need this for type-checking, not
at runtime").

PART 3 — TypeScript for Full-Stack Work


14. TypeScript with React
SYNTAX — typing props
interface CardProps {
title: string;
count: number;
onSelect?: (id: number) => void; // optional callback prop
children?: [Link];
}

function Card({ title, count, onSelect, children }: CardProps) {


return (
<div onClick={() => onSelect?.(count)}>
<h3>{title}</h3>
{children}
</div>
);
}

SYNTAX — typing hooks


const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null); // common pattern — starts
null, later populated

const inputRef = useRef<HTMLInputElement>(null);

interface FetchState<T> {
data: T | null;
loading: boolean;
error: string | null;
}
const [state, setState] = useState<FetchState<Product[]>>({
data: null,
loading: true,
error: null,
});

EXAMPLE — typed API fetch inside a component


interface Product {
id: number;
name: string;
price: number;
}

async function fetchProducts(): Promise<Product[]> {


const res = await fetch("/api/products");
if (![Link]) throw new Error("Failed to fetch products");
const data: Product[] = await [Link]();
return data;
}

fetch() itself returns Promise<Response>, and .json() returns Promise<any> — TypeScript can't know
your API's shape automatically. You annotate it yourself, or generate types from your backend's schema
in bigger projects (OpenAPI, tRPC, or Zod schemas shared across front and back end).

15. TypeScript with [Link] and Express


SETUP
npm install express
npm install -D typescript @types/node @types/express ts-node-dev

@types/* packages ship the type definitions for JS libraries that weren't written in TypeScript
themselves — Express's actual code is plain JS, but @types/express describes its shapes so TypeScript
understands it.
SYNTAX — typed request/response
import express, { Request, Response } from "express";

const app = express();


[Link]([Link]());

interface CreateUserBody {
name: string;
email: string;
}

[Link]("/users", (req: Request<{}, {}, CreateUserBody>, res: Response) => {


const { name, email } = [Link]; // fully typed, autocomplete works
[Link](201).json({ id: 1, name, email });
});

[Link](3000, () => [Link]("Server running on port 3000"));

Request's generic parameters are <Params, ResBody, ReqBody, ReqQuery> in order — here only the
request body type is specified, matching the CreateUserBody shape you defined.
EXAMPLE — a typed service layer
interface User {
id: number;
name: string;
email: string;
}

class UserService {
private users: User[] = [];

create(name: string, email: string): User {


const user: User = { id: [Link] + 1, name, email };
[Link](user);
return user;
}

findById(id: number): User | undefined {


return [Link]((u) => [Link] === id);
}
}

16. Sharing types between frontend and backend, and validating at runtime
THEORY
TypeScript types disappear completely at compile time — they give you zero protection against bad data
arriving from a real network request, form submission, or database. That's what runtime validation
libraries are for, most commonly Zod, often paired with a monorepo so both ends import the same
schema.
EXAMPLE — Zod (very common in current full-stack TS projects)
npm install zod
import { z } from "zod";

const UserSchema = [Link]({


name: [Link]().min(1),
email: [Link]().email(),
});

// the TypeScript type is derived directly from the runtime schema — one source
of truth
type UserInput = [Link]<typeof UserSchema>;

[Link]("/users", (req, res) => {


const result = [Link]([Link]);
if (![Link]) {
return [Link](400).json({ errors: [Link] });
}
const user: UserInput = [Link]; // now safely typed AND runtime-
validated
[Link](201).json(user);
});

Why this matters


TypeScript protects you from mistakes you make while writing code. It cannot protect you from a
malformed request body a real user or attacker sends at runtime — that check has to actually run when
the server is live. Zod (or similar) covers that gap, and deriving the TS type from the schema means you
only define the shape once.

17. Strictness settings worth knowing


Flag What it catches
strictNullChecks Forces you to handle null/undefined explicitly instead of assuming a
value always exists
noImplicitAny Errors on any variable/parameter TS can't infer and you didn't annotate
strictFunctionTypes Stricter checking of function parameter compatibility
noUnusedLocals / Flags dead code — unused variables/params
noUnusedParameters
noImplicitReturns Every code path in a function must explicitly return a value if one
branch does
strict: true in [Link] turns all of these on at once — always start new projects with it on rather
than adding strictness later, which is a much harder migration.

Quick reference — syntax you'll type constantly


Syntax Meaning
string | number Union — value is one of these types
A & B Intersection — value has both shapes
T[] or Array<T> Array of type T
[string, number] Tuple — fixed length and per-position types
value is string Custom type guard return signature
value as Type Type assertion (no runtime check)
Partial<T> / Pick<T,K> / Utility types — transform an existing type
Omit<T,K>
<T,>(x: T): T Generic function
field?: string Optional property
readonly field: string Property can't be reassigned after creation
import type { X } Type-only import, erased from compiled JS
[Link]<typeof Schema> Derive a TS type from a Zod runtime schema

Next step: convert one small existing JS file (a React component or an Express route) into TypeScript end
to end — that's where the type system actually clicks, far more than reading examples does.

You might also like