
TypeScript Best Practices for Modern Web Development
Essential TypeScript best practices: strict typing, generics, utility types, discriminated unions, and avoiding common mistakes. Practical patterns for React, Vue, and Node.js projects.
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!
