TypeScript¶

Table of Contents¶
- 1. Getting Started
- 3. Shaping Your Data
- 4. Functions in TypeScript
- 5. Advanced Types & Generics
- 6. Object Oriented Programming
- 7. Security with TypeScript
Part 1: Getting Started¶
1. Installation¶
Install TypeScript with npm (Node.js package manager). Install it globally so you can run tsc from anywhere.
npm install -g typescript
2. The Compiler-(tsc)¶
Here's a simple TypeScript file to get you started.
greeter.ts
function greet(name: string) {
return `Hello, ${name}!`;
}
const user = "World";
console.log(greet(user));
To compile this, run the TypeScript Compiler (tsc):
tsc greeter.ts
This creates greeter.js plain JavaScript you can run in a browser or with Node.js.
greeter.js (Compiled Output)
function greet(name) {
return "Hello, " + name + "!";
}
var user = "World";
console.log(greet(user));
3. The tsconfig.json File¶
For real projects, use tsconfig.json to configure the compiler. Generate one by running:
tsc --init
This creates a file with many options and helpful comments. Here are the most important ones to know:
{
"compilerOptions": {
/* Target JavaScript Version */
"target": "ES2020", // Compile to modern JavaScript
/* Module System */
"module": "commonjs", // For Node.js, or "ESNext" for browsers
/* Strictness */
"strict": true, // This is the most important setting! It enables all strict type checking options.
/* Output */
"outDir": "./dist", // Where to put the compiled .js files
"rootDir": "./src", // Where to find the source .ts files
"esModuleInterop": true, // Allows better compatibility with CommonJS modules
"forceConsistentCasingInFileNames": true // Prevents case related errors
}
}
With a tsconfig.json in place, you can just run tsc in your project root, and it will compile all your .ts files according to the rules you've set.
Part 2: The Core of TypeScript¶
4. Basic Types: string, number, boolean¶
You can explicitly add type annotations to your variables. If you don't, TypeScript will try to infer the type.
let framework: string = "TypeScript";
let version: number = 4.9;
let isAwesome: boolean = true;
// Type Inference
let inferredString = "I am a string"; // TypeScript knows this is a string
// inferredString = 123; // Error: Type 'number' is not assignable to type 'string'.
5. Typing Arrays and Tuples¶
- Arrays: You can type arrays in two ways.
- Tuples: Fixed length, fixed type arrays.
// An array of numbers
let numbers: number[] = [1, 2, 3];
let moreNumbers: Array<number> = [4, 5, 6];
// A tuple: an array with a specific sequence of types
let user: [number, string];
user = [1, "Alice"];
// user = ["Bob", 2]; // Error!
6. The any, unknown, and never Types¶
any: The "escape hatch." A variable of typeanycan be anything. It disables all type checking for that variable. Usinganydefeats the purpose of TypeScript and should be avoided whenever possible.unknown: The type safe alternative toany. A variable of typeunknowncan be anything, but you must perform type checks before you can use it.never: Represents a value that will never occur. Used for functions that always throw an error or have an infinite loop.
let notSure: unknown = 4;
// console.log(notSure.toFixed(2)); // Error: Object is of type 'unknown'.
if (typeof notSure === "number") {
// This is fine, we've checked the type.
console.log(notSure.toFixed(2)); // "4.00"
}
7. The null, undefined, and void Types¶
nullandundefined: These are types with only one value:nullandundefined, respectively. With"strictNullChecks": trueintsconfig.json(part of"strict": true), TypeScript will force you to handle cases where a value could benullorundefined.void: The return type of a function that does not return a value.
function logMessage(message: string): void {
console.log(message);
}
Part 3: Shaping Your Data¶
8. Interfaces: Defining Object Shapes¶
An interface is a way to define a "contract" for an object's shape. It specifies the property names and their types.
interface User {
id: number;
username: string;
isActive: boolean;
readonly registrationDate: Date; // Property cannot be changed after creation
bio?: string; // The `?` makes this property optional
}
const user1: User = {
id: 1,
username: "alice",
isActive: true,
registrationDate: new Date(),
};
// user1.registrationDate = new Date(); // Error: Cannot assign to 'registrationDate' because it is a read only property.
9. Type Aliases: Naming Your Types¶
A type alias lets you create a new name for a type. It's useful for simplifying complex types like unions.
type UserID = number | string;
function getUser(id: UserID) {
// ...
}
getUser(123); // OK
getUser("abc-123"); // OK
interface vs. type: For defining object shapes, they are very similar. A key difference is that interfaces can be extended by other interfaces, while types cannot. A common convention is to use interface for object shapes and type for all other custom types.
10. Union Types-(|)¶
A union type allows a variable to be one of several types.
function printId(id: number | string) {
if (typeof id === "string") {
// Here, TypeScript knows `id` is a string
console.log(id.toUpperCase());
} else {
// Here, TypeScript knows `id` is a number
console.log(id);
}
}
11. Intersection Types-(&)¶
An intersection type combines multiple types into one.
interface Loggable {
log(): void;
}
interface Serializable {
serialize(): string;
}
// The variable `myObject` must have both a `log` and a `serialize` method.
type LoggableAndSerializable = Loggable & Serializable;
Part 4: Functions in TypeScript¶
12. Typing Parameters and Return Values¶
TypeScript allows you to be explicit about what your functions expect and what they will return.
// This function takes two numbers and is explicitly typed to return a number.
function add(a: number, b: number): number {
return a + b;
}
// TypeScript can also infer the return type, but being explicit is often clearer.
function subtract(a: number, b: number) { // Return type is inferred as `number`
return a b;
}
13. Optional and Default Parameters¶
- Add a
?after a parameter name to make it optional. - Provide a default value to a parameter to make it optional and give it a value if none is provided.
function greet(name: string, greeting?: string) {
if (greeting) {
return `${greeting}, ${name}!`;
} else {
return `Hello, ${name}!`;
}
}
function power(base: number, exponent: number = 2): number {
return Math.pow(base, exponent);
}
console.log(power(3)); // 9 (exponent defaults to 2)
console.log(power(3, 3)); // 27
Part 5: Advanced Types & Generics¶
14. Generics-(<T>): Creating Reusable Components¶
Generics allow you to create components (like functions or classes) that can work with a variety of types rather than a single one. This is a core feature for creating reusable and type safe code.
// Without generics, you might have to use `any`, which is not type safe.
function identity_any(arg: any): any {
return arg;
}
// With generics, we create a type variable `T`.
// This function takes an argument of type `T` and returns a value of type `T`.
function identity<T>(arg: T): T {
return arg;
}
// TypeScript can infer the type of T
let output1 = identity("myString"); // output1 is of type `string`
// Or you can set it explicitly
let output2 = identity<number>(123); // output2 is of type `number`
15. Enums: Named Constants¶
Enums allow you to define a set of named constants. They can make your code more readable.
enum LogLevel {
INFO,
WARN,
ERROR,
DEBUG
}
function log(level: LogLevel, message: string) {
// ...
}
log(LogLevel.INFO, "User logged in.");
Part 6: Object Oriented Programming¶
TypeScript fully supports object oriented programming with classes, just like Java or C#.
16. Classes, Fields, and Methods¶
class Player {
// Fields with types
username: string;
health: number;
// Constructor
constructor(username: string) {
this.username = username;
this.health = 100;
}
// Method
takeDamage(amount: number): void {
this.health -= amount;
console.log(`${this.username} took ${amount} damage. Health is now ${this.health}.`);
}
}
const player1 = new Player("Gopher");
player1.takeDamage(10);
17. Access Modifiers and readonly¶
public: (Default) Can be accessed from anywhere.private: Can only be accessed from within the same class.protected: Can be accessed from within the class and by subclasses.readonly: Can only be set in the constructor.
class User {
public readonly id: number;
private secret: string;
constructor(id: number) {
this.id = id;
this.secret = "my secret";
}
}
18. Inheritance-(extends) and Interfaces (implements)¶
interface IAttack {
attack(target: Character): void;
}
class Character {
constructor(public name: string, public health: number) {}
}
class Hero extends Character implements IAttack {
constructor(name: string) {
super(name, 100); // Call the parent constructor
}
attack(target: Character): void {
console.log(`${this.name} strikes ${target.name}!`);
}
}
Part 7: Security with TypeScript¶
19. How Type Safety Improves Security¶
While not a silver bullet, a strong type system is a powerful security tool. It prevents entire classes of runtime errors that can lead to unpredictable behavior and vulnerabilities.
- Null Pointer/Reference Errors: With
"strictNullChecks": true, the compiler forces you to check fornullorundefinedbefore you can use a variable, preventing crashes and potential logic flaws. - Type Confusion Bugs: TypeScript ensures you can't accidentally use a
numberwhere astringis expected, or vice versa. In lower level languages, this type of bug can lead to memory corruption. - API Contract Enforcement: When working with typed objects, you get compile time guarantees that you are accessing properties that actually exist, preventing
undefined is not a functionerrors.
20. The any Anti-Pattern¶
Using any is like telling the TypeScript compiler, "Trust me, I know what I'm doing." It completely disables type checking for that variable. This can hide bugs and vulnerabilities that TypeScript would otherwise catch.
function processData(data: any) {
// The compiler has no idea what `data` is. It will allow anything.
console.log(data.name.toUpperCase()); // This will crash if `data.name` doesn't exist!
}
Solution: Use unknown and perform type checks, or better yet, define an interface for the expected shape of data.
21. The Golden Rule: Runtime Validation is Still Required¶
This is the most important security lesson for any TypeScript developer. TypeScript types are a compile time tool. They are completely erased when your code is compiled to JavaScript.
This means TypeScript offers zero protection against malicious or malformed data coming from external sources at runtime.
interface User { name: string; }
// Imagine this function is in an Express.js API handler
function createUser(req: Request, res: Response) {
// You might THINK `req.body` is a `User` because you typed it.
const user: User = req.body;
// But an attacker can send `req.body` as ` { "name": 123 } ` or ` { } `.
// At runtime, `user.name` could be a number or undefined.
// The `toUpperCase()` call will crash your server!
console.log(user.name.toUpperCase());
}
22. Runtime Validation with zod¶
To solve the runtime validation problem, you need a library that can parse and validate untrusted data. Zod is a fantastic, popular choice.
First, install it: npm install zod
import { z } from "zod";
// 1. Define a schema that represents the shape and types you expect.
const UserSchema = z.object({
username: z.string().min(3, "Username must be at least 3 characters"),
email: z.string().email(),
age: z.number().positive().optional(),
});
// 2. Infer the TypeScript type directly from the schema.
// Now your runtime schema and compile time type are always in sync!
type User = z.infer<typeof UserSchema>;
// 3. Parse untrusted data.
const untrustedData = { username: "test", email: "not an email" };
try {
const validatedUser = UserSchema.parse(untrustedData);
// If `parse` succeeds, you have a fully typed and validated object.
console.log("Validation successful:", validatedUser);
} catch (error) {
// If validation fails, Zod throws a detailed error.
console.error("Validation failed:", error);
}
Part 8: Cookbook¶
23. Cookbook: A Complex User Profile Interface¶
This shows how to combine interfaces to define a complex data structure.
interface Address {
street: string;
city: string;
zipCode: string;
country: string;
}
enum UserRole {
ADMIN = "ADMIN",
EDITOR = "EDITOR",
VIEWER = "VIEWER",
}
interface UserProfile {
readonly id: string;
username: string;
email: string;
address: Address;
roles: UserRole[];
lastLogin?: Date;
}
const user: UserProfile = {
id: "uuid-12345",
username: "0x1RIS",
email: "0x1ris@example.com",
address: {
street: "123 Hacker Way",
city: "Cyber City",
zipCode: "1337",
country: "NET",
},
roles: [UserRole.ADMIN, UserRole.EDITOR],
};
24. Cookbook: A Generic API Client¶
This generic function can fetch data from any API endpoint and will return a properly typed object, assuming the data is valid.
import { z, ZodType } from "zod";
// Use a runtime validator like Zod to ensure the API response is what you expect.
async function fetchAndValidate<T>(url: string, schema: ZodType<T>): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Parse the data against the schema. This is the runtime validation step!
return schema.parse(data);
}
// Example usage:
const PostSchema = z.object({
userId: z.number(),
id: z.number(),
title: z.string(),
body: z.string(),
});
async function getPost() {
try {
const post = await fetchAndValidate("https://jsonplaceholder.typicode.com/posts/1", PostSchema);
// `post` is now fully typed and validated!
console.log("Post Title:", post.title);
} catch (error) {
console.error(error);
}
}
getPost();
25. Cookbook: A Simple Class Hierarchy¶
This example shows basic OOP principles with classes, inheritance, and access modifiers.
abstract class Vehicle {
constructor(protected readonly vin: string) {}
drive(): void {
console.log("The vehicle is moving.");
}
abstract honk(): void; // Must be implemented by subclasses
}
class Car extends Vehicle {
private isEngineOn: boolean = false;
constructor(vin: string, public color: string) {
super(vin);
}
startEngine() {
this.isEngineOn = true;
console.log("Engine started.");
}
honk(): void {
console.log("Beep beep!");
}
}
const myCar = new Car("VIN12345", "blue");
myCar.startEngine();
myCar.drive();
myCar.honk();
// console.log(myCar.vin); // Error: 'vin' is protected and only accessible within class 'Vehicle' and its subclasses.