TypeScript Best Practices
Priority: P1 (OPERATIONAL)
Idiomatic patterns for writing clean, maintainable TypeScript code.
Implementation Guidelines
- Naming Conventions:
- PascalCase for classes, interfaces, types, enums
- camelCase for variables, functions, methods, parameters
- UPPER_SNAKE_CASE for constants
- Prefix interfaces with
Ionly when necessary for disambiguation
- Functions:
- Prefer arrow functions for callbacks and short functions
- Use regular functions for methods and exported functions
- Always specify return types for public APIs
- Modules:
- One export per file for major components/classes
- Use named exports over default exports for better refactoring
- Organize imports: external -> internal -> relative
- Async/Await:
- Prefer
async/awaitover raw Promises - Always handle errors with try/catch in async functions
- Use
Promise.all()for parallel operations
- Prefer
- Classes:
- Use
private/protected/publicmodifiers explicitly - Prefer composition over inheritance
- Use
readonlyfor properties that don't change after construction
- Use
- Exhaustiveness Checking: Use
nevertype inswitchcases. - Assertion Functions: Use
assertsfor runtime type validation. - Optional Properties: Use
?:, not| undefined. - Type Imports: Use
import typefor tree-shaking.
Anti-Patterns
- No Default Exports: Use named exports.
- No Implicit Returns: Specify return types.
- No Unused Variables: Enable
noUnusedLocals. - No
require: Use ES6import. - No Empty Interfaces: Use
typeor non-empty interface.
Code
// Named Export + Immutable Interface
export interface User {
readonly id: string;
name: string;
}
// Exhaustive Check
function getStatus(s: 'ok' | 'fail') {
switch (s) {
case 'ok': return 'OK';
case 'fail': return 'Fail';
default: const _chk: never = s; return _chk;
}
}
// Assertion
function assertDefined<T>(val: T): asserts val is NonNullable<T> {
if (val == null) throw new Error("Defined expected");
}
``` private readonly repository: UserRepository;
constructor(repository: UserRepository) {
this.repository = repository;
}
async getUser(id: string): Promise<UserProfile> {
try {
return await this.repository.findById(id);
} catch (error) {
throw new Error(`Failed to get user: ${error.message}`);
}
}
}
// Organize imports
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { UserRepository } from '@/repositories/user.repository';
import { Logger } from '@/utils/logger';
// Type-only imports
import type { Request, Response } from 'express';
Reference & Examples
For project structure and module organization: See references/REFERENCE.md.
Related Topics
language | tooling | security