Featured Post

TypeScript Best Practices for Production Applications

Learn essential TypeScript best practices and patterns to write type-safe, maintainable code for production-ready applications.

2 min read
By Claude

TypeScript Best Practices for Production Applications

TypeScript has become the de facto standard for building large-scale JavaScript applications. In this guide, I'll share best practices and patterns that I've learned from building production applications.

1. Leverage Type Inference

TypeScript's type inference is powerful. Don't over-annotate when TypeScript can infer types automatically:

// ❌ Redundant type annotation
const message: string = 'Hello, World!'
const numbers: number[] = [1, 2, 3]

// ✅ Let TypeScript infer
const message = 'Hello, World!'
const numbers = [1, 2, 3]

// ✅ Annotate when needed
function greet(name: string): string {
  return `Hello, ${name}!`
}

2. Use Strict Mode

Always enable strict mode in your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}

3. Prefer Interfaces Over Type Aliases for Objects

Interfaces are more performant and provide better error messages:

// ✅ Preferred
interface User {
  id: string
  name: string
  email: string
}

// ⚠️ Use for unions, intersections, or primitives
type Status = 'active' | 'inactive' | 'pending'
type ID = string | number

4. Use Discriminated Unions

Discriminated unions make type narrowing powerful and safe:

type Success<T> = {
  status: 'success'
  data: T
}

type Error = {
  status: 'error'
  error: string
}

type Result<T> = Success<T> | Error

function handleResult<T>(result: Result<T>) {
  if (result.status === 'success') {
    // TypeScript knows result.data exists
    console.log(result.data)
  } else {
    // TypeScript knows result.error exists
    console.error(result.error)
  }
}

5. Avoid the any Type

The any type defeats the purpose of TypeScript. Use unknown instead:

// ❌ Avoid
function processValue(value: any) {
  return value.toString() // No type safety
}

// ✅ Better
function processValue(value: unknown) {
  if (typeof value === 'string') {
    return value.toUpperCase()
  }
  if (typeof value === 'number') {
    return value.toFixed(2)
  }
  throw new Error('Unsupported type')
}

6. Use Utility Types

TypeScript provides powerful utility types. Master them:

interface User {
  id: string
  name: string
  email: string
  password: string
}

// Pick specific properties
type PublicUser = Pick<User, 'id' | 'name'>

// Omit properties
type UserWithoutPassword = Omit<User, 'password'>

// Make all properties optional
type PartialUser = Partial<User>

// Make all properties required
type RequiredUser = Required<User>

// Make all properties readonly
type ReadonlyUser = Readonly<User>

7. Use const Assertions

Const assertions provide more precise 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"]

// Great for configuration objects
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
} as const
// All properties are readonly

8. Generic Constraints

Use generic constraints to make your functions more flexible and type-safe:

interface HasId {
  id: string
}

function findById<T extends HasId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id)
}

// Works with any object that has an id
const users = [
  { id: '1', name: 'Alice' },
  { id: '2', name: 'Bob' },
]
const user = findById(users, '1') // Type: { id: string; name: string } | undefined

9. Type Guards

Create custom type guards for better type narrowing:

interface Dog {
  type: 'dog'
  bark(): void
}

interface Cat {
  type: 'cat'
  meow(): void
}

type Pet = Dog | Cat

// Type guard
function isDog(pet: Pet): pet is Dog {
  return pet.type === 'dog'
}

function handlePet(pet: Pet) {
  if (isDog(pet)) {
    pet.bark() // TypeScript knows pet is Dog
  } else {
    pet.meow() // TypeScript knows pet is Cat
  }
}

10. Avoid Enums (Use Union Types Instead)

Union types are more flexible and tree-shakeable:

// ❌ Enum (adds runtime code)
enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE',
  Pending = 'PENDING',
}

// ✅ Union type (no runtime cost)
const Status = {
  Active: 'ACTIVE',
  Inactive: 'INACTIVE',
  Pending: 'PENDING',
} as const

type Status = (typeof Status)[keyof typeof Status]

11. Use Template Literal Types

Template literal types are powerful for creating precise string types:

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
type Endpoint = '/users' | '/posts' | '/comments'

// Create all possible combinations
type Route = `${HTTPMethod} ${Endpoint}`
// 'GET /users' | 'POST /users' | 'GET /posts' | ...

type EventName = 'click' | 'focus' | 'blur'
type EventHandler = `on${Capitalize<EventName>}`
// 'onClick' | 'onFocus' | 'onBlur'

12. Proper Error Handling

Type your errors properly:

class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number = 500
  ) {
    super(message)
    this.name = 'AppError'
  }
}

function handleError(error: unknown): AppError {
  if (error instanceof AppError) {
    return error
  }

  if (error instanceof Error) {
    return new AppError(error.message, 'UNKNOWN_ERROR')
  }

  return new AppError('An unknown error occurred', 'UNKNOWN_ERROR')
}

Conclusion

These TypeScript best practices will help you write more maintainable, type-safe code. Remember:

  • Enable strict mode
  • Leverage type inference
  • Use discriminated unions
  • Avoid any, prefer unknown
  • Master utility types
  • Create custom type guards

TypeScript is a powerful tool that becomes even more valuable when used correctly. Start implementing these practices in your projects today!

Additional Resources

Happy coding! 🚀

Published on December 20, 2024

Related Posts

Enjoyed this post?

Subscribe to get notified when I publish new content about web development and technology.