Skip to content

Instantly share code, notes, and snippets.

@georgebrata
Created November 18, 2024 10:09
Show Gist options
  • Select an option

  • Save georgebrata/6cbc5e012feaf82915bcdba5e8c0feb2 to your computer and use it in GitHub Desktop.

Select an option

Save georgebrata/6cbc5e012feaf82915bcdba5e8c0feb2 to your computer and use it in GitHub Desktop.
Abstract API Service with typescript example

The design pattern below is Template Method Pattern used in combination with the Abstract Class Pattern. It is a common practice in web development to create an Abstract API Class that defines a general structure for handling API requests and then extend this abstract class for each specific entity. Here's how you can implement this same pattern in TypeScript:

TypeScript Example

// Abstract API Class (Template Method Pattern)
abstract class AbstractApi {
  protected baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  // Generic request method
  protected async request<T>(
    endpoint: string,
    method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
    data?: any
  ): Promise<T> {
    const options: RequestInit = {
      method,
      headers: {
        'Content-Type': 'application/json',
      },
      body: data ? JSON.stringify(data) : undefined,
    };

    try {
      const response = await fetch(`${this.baseUrl}/${endpoint}`, options);

      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
      }

      return (await response.json()) as T;
    } catch (error) {
      console.error('API request failed:', error);
      throw error;
    }
  }

  // CRUD methods
  protected get<T>(endpoint: string): Promise<T> {
    return this.request<T>(endpoint, 'GET');
  }

  protected post<T>(endpoint: string, data: any): Promise<T> {
    return this.request<T>(endpoint, 'POST', data);
  }

  protected put<T>(endpoint: string, data: any): Promise<T> {
    return this.request<T>(endpoint, 'PUT', data);
  }

  protected delete<T>(endpoint: string): Promise<T> {
    return this.request<T>(endpoint, 'DELETE');
  }
}

// User API Class (Concrete Implementation)
interface User {
  id: number;
  name: string;
  email: string;
}

class UserApi extends AbstractApi {
  constructor() {
    super('/api/users');
  }

  // Entity-specific methods
  public getUserById(userId: number): Promise<User> {
    return this.get<User>(`${userId}`);
  }

  public createUser(userData: Partial<User>): Promise<User> {
    return this.post<User>('', userData);
  }

  public updateUser(userId: number, userData: Partial<User>): Promise<User> {
    return this.put<User>(`${userId}`, userData);
  }

  public deleteUser(userId: number): Promise<void> {
    return this.delete<void>(`${userId}`);
  }
}

// Example usage
const userApi = new UserApi();

userApi.getUserById(1).then((user) => console.log(user));

Explanation:

1. Abstract API Class (AbstractApi):

  • Defined as an abstract class to ensure it cannot be instantiated directly.
  • Contains a generic request method for handling API requests, utilizing TypeScript generics (<T>) for typed responses.
  • Provides reusable CRUD methods (get, post, put, delete) with TypeScript typing for safer API calls.

2. Concrete API Class (UserApi):

  • Extends the abstract class and implements specific methods tailored for the "User" entity.
  • Uses TypeScript interfaces (e.g., User) to define expected data structures.

3. TypeScript Features Used:

  • Generics (<T>): Allow the request method to return different types depending on the API call.
  • Abstract Classes: Enforces extending classes to implement custom behavior without direct instantiation.
  • Interfaces (User): Defines the structure of the expected API response, making the code type-safe.

Benefits:

  • Type Safety: Errors are caught at compile time due to strict typing.
  • Reusability: Common API logic is centralized in the abstract class, reducing duplication.
  • Scalability: Easily extendable for additional entities (e.g., ProductApi, OrderApi).

This approach improves maintainability and ensures type-safe API interactions in TypeScript applications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment