Unlock the real power of TypeScript with advanced constructs that enable flexible, powerful type expressions.
Welcome to Union, Intersection, and Literal Types in TypeScript! These advanced type concepts will help you create more flexible and safe APIs. Let's explore each of them in detail.
A union type allows a variable to be one of several types. This is incredibly useful when you need flexibility in your API endpoints or data structures.
function greeting(message: string | number) {
if (typeof message === 'string') {
return `Hello, ${message}!`;
}
return `Hello, Number ${message}!`;
}
greeting('World'); // 'Hello, World!'
greeting(42); // 'Hello, Number 42!'
interface User {
id: number;
name: string;
}
interface Error {
code: string;
message: string;
}
function fetchResource(id: number): Promise<User | Error> {
// API implementation
}
An intersection type combines multiple interfaces into one, enforcing all their properties. This is powerful for creating highly specific types.
interface Square {
side: number;
}
interface Colored {
color: string;
}
const square: Square & Colored = {
side: 5,
color: 'red'
};
interface BaseDTO {
id: number;
createdAt: Date;
}
interface User extends BaseDTO {
username: string;
email: string;
}
const createUser = (data: User): Promise<User & { success: boolean }> => {
// Implementation
};
A literal type restricts a value to a specific literal. This is perfect for creating safe enums or validation schemas.
type Status = 'active' | 'inactive';
function updateStatus(status: Status) {
// API implementation
}
updateStatus('active'); // Valid
updateStatus('pending'); // Error
interface CreateOrderDTO {
status: 'pending' | 'confirmed';
type: 'delivery' | 'pickup';
amount: number;
}
const order: CreateOrderDTO = {
status: 'pending', // Valid
type: 'delivery', // Valid
amount: 100.50
};
A tagged union uses a common property (like type or kind) to distinguish between different variants. This enables safe pattern matching in TypeScript.
interface Animal {
type: 'cat' | 'dog';
name: string;
}
const animals: Animal[] = [
{ type: 'cat', name: 'Whiskers' },
{ type: 'dog', name: 'Buddy' }
];
interface SuccessResponse {
status: 'success';
data: any;
}
interface ErrorResponse {
status: 'error';
message: string;
}
function handleResponse(response: SuccessResponse | ErrorResponse) {
if (response.status === 'success') {
return processSuccess(response);
}
return handleError(response);
}
Advanced types are essential in modern NestJS applications. They enable: - Safe API endpoints with strict type validation - Flexible DTOs for complex data structures - Pattern matching and exhaustive checks - Type-safe enums and validation schemas
Welcome to Type Guards and Narrowing! In this chapter, we'll explore how to use TypeScript's powerful type system to ensure precise control flow analysis. You'll learn about built-in type guards like typeof⌖,
instanceof⌖, and ⌘in⌖ checks, as well as creating custom type guards to handle complex scenarios.
Type guards are mechanisms that allow you to °narrow⌖ the type of a variable within a specific scope. This is especially useful when working with union types, as it enables TypeScript to understand exactly which type a variable represents at a particular point in your code.
typeof⌖,
instanceof⌖, and custom helper functions.TypeScript provides several built-in type guards that you can use to check the type of a variable.
// Example using typeof
function getType(value: unknown) {
if (typeof value === 'string') {
return 'This is a string';
}
if (typeof value === 'number') {
return 'This is a number';
}
return 'Unknown type';
}
For more complex scenarios, you can create custom type guards using helper functions or classes.
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// Usage
function getStringLength(value: unknown) {
if (isString(value)) {
return value.length;
}
throw new Error('Value is not a string');
}
interface User {
type: 'user';
id: string;
name: string;
}
interface Guest {
type: 'guest';
sessionId: string;
}
function getUserInfo(userOrGuest: User | Guest) {
if (userOrGuest.type === 'user') {
return `User: ${userOrGuest.name}`;
}
return `Guest session ID: ${userOrGuest.sessionId}`;
}
Type guards are essential in applications where data validation and type safety are critical, such as: - Web APIs handling different request types - User interfaces managing various component states - Data processing pipelines ensuring consistent input formats
Welcome to the world of Conditional and Mapped Types! These powerful tools allow you to create dynamic, reusable type logic that can adapt to different scenarios. Using extends
, keyof
, and in
, you can transform types in sophisticated ways.
keyof
to access property names of a typein
for checking if a type extends anotherLet's explore these concepts through practical examples.
Conditional types allow you to create type logic that changes based on conditions. The basic syntax is: Type1 extends Type2 ? ResultIfTrue : ResultIfFalse.
// Example of a conditional type
type ConditionalExample<T> = T extends string
? string // If T is a string, use string
: number; // Otherwise, use number
Here are some common patterns:
T extends { prop: any } ? ... : ...
T extends string ? ... : ...
The keyof
operator returns the union of keys in a type. The in
operator checks if one type extends another.
// Example of keyof
type Keys = keyof { name: string; age: number }; // 'name' | 'age'
// Example of in
type IsString<T> = T extends string ? true : false;
These operators are especially useful for creating generic utilities.
Mapped types allow you to transform existing types by mapping over their properties. The syntax is similar to array.map(), but for types.
// Example of a mapped type
type Partial<T> = {
[P in keyof T]?: T[P]; // Makes all properties optional
};
Common use cases include:
These techniques are essential for building flexible and reusable type definitions.
extends
for conditional checks whenever possibleWelcome to Template Literal Types in TypeScript! In this chapter, you'll learn how to create type-safe strings using template literals. These advanced types allow you to dynamically construct string patterns with precision and safety.
Template literal types are a feature in TypeScript that allows you to define string types using template literals. These types can include static parts and dynamic parts, providing precise control over the structure of your strings.
const filePath: `src/${string}.ts` = 'src/example.ts';
Here's how to create a simple template literal type:
// Define a file path pattern
const filePath: `src/${string}.ts` = 'src/example.ts';
// TypeScript will validate the pattern at compile-time
const invalidPath: `src/${string}.ts` = 'example.txt'; // Error!
Use template literal types to enforce specific string patterns, such as API endpoints or CSS class names.
// Define an API endpoint pattern
const apiEndpoint: `api/v${number}/${string}` = 'api/v1/users';
// Only valid numbers and paths are allowed
const invalidEndpoint: `api/v${number}/${string}` = 'api/vx/users'; // Error!
Question 1 of 19