Skip to main content

architectural-best-practices

TypeScript Architectural Best Practices:

  • Consistent Coding Conventions:

    // Class example
    class UserAccount {
    private firstName: string;
    private lastName: string;

    constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
    }
    }

    // Function example
    function calculateArea(radius: number): number {
    return Math.PI * radius * radius;
    }

    // File name example: user-account.ts
  • Leverage Modularization:

    // math-util.ts
    export function add(a: number, b: number): number {
    return a + b;
    }

    // calculator.ts
    import { add } from './math-util';

    export class Calculator {
    sum(a: number, b: number): number {
    return add(a, b);
    }
    }
  • Use TypeScript’s Type System Fully:

    interface IUser {
    name: string;
    age: number;
    }

    enum Color {
    Red,
    Green,
    Blue
    }

    type UserOrString = IUser | string;
  • Implement Interface Segregation:

    interface IReadable {
    read(): string;
    }

    interface IWriteable {
    write(content: string): void;
    }

    class Document implements IReadable, IWriteable {
    read(): string {
    return "Reading content";
    }
    write(content: string): void {
    console.log(`Writing content: ${content}`);
    }
    }
  • Prefer Composition Over Inheritance:

    class Engine {
    start() {
    console.log('Engine starting...');
    }
    }

    class Car {
    engine: Engine;

    constructor(engine: Engine) {
    this.engine = engine;
    }

    start() {
    this.engine.start();
    }
    }

    const engine = new Engine();
    const car = new Car(engine);
    car.start();
  • Employ Dependency Injection:

    class Logger {
    log(message: string) {
    console.log(message);
    }
    }

    class AuthenticationService {
    constructor(private logger: Logger) {}

    authenticate() {
    this.logger.log('Authentication started');
    // Authentication logic...
    }
    }

    const logger = new Logger();
    const authService = new AuthenticationService(logger);
    authService.authenticate();
  • Centralize HTTP Communications:

    import axios from 'axios';

    class APIService {
    async getUser(userId: string) {
    try {
    const response = await axios.get(`/api/users/${userId}`);
    return response.data;
    } catch (error) {
    console.error('Error fetching user:', error);
    throw error;
    }
    }
    }
  • Enforce Strict Compiler Options:

    {
    "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    // Other strict options...
    }
    }

    The above JSON is an example of setting strict compiler options in a tsconfig.json file.

  • Use Linters and Formatters:

    Set up ESLint with TypeScript support to check for syntax and logical errors. Add Prettier to format code according to defined style rules. Both can be integrated into your IDE and CI/CD pipeline for automatic checking and formatting on save or before commits.
  • Embrace Async/Await for Asynchronous Code:

    async function fetchData(url: string): Promise<Data> {
    try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
    } catch (error) {
    console.error('An error occurred:', error);
    throw error;
    }
    }
  • Implement Robust Error Handling:

    class DatabaseError extends Error {
    constructor(message: string) {
    super(message);
    this.name = 'DatabaseError';
    }
    }

    function connectToDatabase(): void {
    try {
    // Database connection logic
    } catch (error) {
    throw new DatabaseError('Failed to connect to the database');
    }
    }
  • Automate Code Quality Checks:

    Configure a CI pipeline with GitHub Actions or GitLab CI that runs ESLint, Prettier, and your test suite on every push or pull request to ensure that code meets quality standards and passes all tests.
  • Create and Maintain Documentation:

    Use typedoc to generate documentation automatically from TypeScript comments. Keep a README file and other markdown documents in your repository to explain the project structure and setup instructions.
  • Optimize Build Process:

    {
    "compilerOptions": {
    "incremental": true,
    "outDir": "./dist"
    // Additional options for optimizing the build
    }
    }

    Configure the tsconfig.json file to use incremental compilation to improve build times for large projects.

  • Test Thoroughly:

    import { add } from './math-utils';
    import { expect } from 'chai';

    describe('math-utils', () => {
    it('should add two numbers correctly', () => {
    const result = add(2, 3);
    expect(result).to.equal(5);
    });
    });

    Write tests using a framework like Mocha with Chai for assertions, taking advantage of TypeScript's type system to ensure the correct types are used and returned.