TypeScript's advanced type system provides powerful tools for building type-safe enterprise applications. This guide explores sophisticated patterns that can improve code quality and developer experience.
1. Conditional Types
Conditional types enable creating types that depend on conditions, making your type system more flexible and expressive.
type ApiResponse<T> = T extends string
? { message: T }
: T extends number
? { count: T }
: { data: T };
// Usage
type StringResponse = ApiResponse<string>; // { message: string }
type NumberResponse = ApiResponse<number> // { count: number }
type ObjectResponse = ApiResponse<User>; // { data: User }
2. Mapped Types
Transform existing types by mapping over their properties.
// Make all properties optional
type Partial<T> = {
[P in keyof T]?: T[P];
};
// Make all properties readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Pick specific properties
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
3. Template Literal Types
Create types based on string patterns for better API design.
type EventName<T extends string> = `on${Capitalize<T>}`;
type Handlers = {
[K in EventName<'click' | 'change' | 'submit'>]: () => void;
};
// Results in:
// {
// onClick: () => void;
// onChange: () => void;
// onSubmit: () => void;
// }
4. Utility Types for APIs
Create reusable utility types for common API patterns.
// API endpoint configuration
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint<TPath extends string, TMethod extends HttpMethod> = {
path: TPath;
method: TMethod;
};
type ApiEndpoints = {
getUser: Endpoint<'/users/:id', 'GET'>;
createUser: Endpoint<'/users', 'POST'>;
updateUser: Endpoint<'/users/:id', 'PUT'>;
};
5. Brand Types for Type Safety
Use brand types to distinguish between similar types.
type UserId = string & { readonly brand: unique symbol };
type ProductId = string & { readonly brand: unique symbol };
function getUser(id: UserId) {
// Implementation
}
function getProduct(id: ProductId) {
// Implementation
}
// This prevents mixing up IDs
const userId = 'user123' as UserId;
const productId = 'product456' as ProductId;
getUser(productId); // Type error!
6. Advanced Function Overloading
Create type-safe function overloads for different parameter combinations.
function processData(data: string): string;
function processData(data: number): number;
function processData(data: boolean): boolean;
function processData<T extends object>(data: T): T;
function processData(data: unknown): unknown {
// Implementation handles all cases
return data;
}
7. Recursive Types
Handle nested data structures with recursive type definitions.
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
type NestedConfig = {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
};
type ReadonlyConfig = DeepReadonly<NestedConfig>;
8. Type Guards and Discriminated Unions
Implement robust type checking for complex data structures.
type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: any };
type ErrorState = { status: 'error'; error: string };
type AppState = LoadingState | SuccessState | ErrorState;
function isSuccessState(state: AppState): state is SuccessState {
return state.status === 'success';
}
function handleState(state: AppState) {
if (isSuccessState(state)) {
// TypeScript knows state.data exists here
console.log(state.data);
}
}
Best Practices for Enterprise TypeScript
- Use strict mode configuration
- Leverage branded types for domain modeling
- Create reusable utility types
- Document complex types with comments
- Use discriminated unions for state management
- Implement proper error handling types
These advanced TypeScript patterns enable building more maintainable, type-safe enterprise applications while improving developer productivity and reducing runtime errors.