TypeScript Best Practices for Modern Web Development
Level up your TypeScript skills with these essential best practices, tips, and patterns for writing type-safe, maintainable code.
TypeScript Best Practices
TypeScript has become the de facto standard for building large-scale JavaScript applications. Let's explore best practices that will make your code more robust and maintainable.
Type Safety First
Use Strict Mode
Always enable strict mode in your tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
Avoid any
The any type defeats the purpose of TypeScript. Use unknown instead:
// ❌ Bad
function processData(data: any) {
return data.value;
}
// ✅ Good
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value;
}
throw new Error('Invalid data');
}
Interface vs Type
Both work, but here's when to use each:
// Use interfaces for object shapes
interface User {
id: number;
name: string;
email: string;
}
// Use types for unions, intersections, and utilities
type Status = 'pending' | 'approved' | 'rejected';
type UserWithStatus = User & { status: Status };
Utility Types
TypeScript provides powerful utility types:
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Pick specific properties
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// Omit properties
type UserWithoutPassword = Omit<User, 'password'>;
// Make all properties optional
type PartialUser = Partial<User>;
// Make all properties required
type RequiredUser = Required<Partial<User>>;
Generic Functions
Write reusable, type-safe functions:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'John' };
const name = getProperty(user, 'name'); // Type: string
Discriminated Unions
Handle different states elegantly:
type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: string };
type ErrorState = { status: 'error'; error: Error };
type State = LoadingState | SuccessState | ErrorState;
function handleState(state: State) {
switch (state.status) {
case 'loading':
return 'Loading...';
case 'success':
return state.data; // TypeScript knows data exists
case 'error':
return state.error.message; // TypeScript knows error exists
}
}
Const Assertions
Preserve literal types:
// Without const assertion
const colors = ['red', 'green', 'blue']; // Type: string[]
// With const assertion
const colors = ['red', 'green', 'blue'] as const; // Type: readonly ["red", "green", "blue"]
Conclusion
TypeScript is a powerful tool that catches bugs before they reach production. Follow these best practices and your code will be more maintainable, refactorable, and bug-free!
