API

Information about the API package

The API package contains wrappers for API calls to our backend.

It is strongly recommended you do not make direct calls from the frontend using fetch or axios in order to maintain consistency and type-checking throughout the app. Using the API package ensures that types used are correct and also may inject some client-side processing if needed.

Resource

We divide API endpoints into "Resources" which is a class that instantiates wrappers for the HTTP methods at the base API routes and allows you to add additional API routes.

export class Resource<T extends object> implements ResourceInterface<DTO<T>> {
  public baseURL: string;
  constructor(baseURL: string) { this.baseURL = baseURL; }

  async get(id: string | number, params: object = {}): Promise<DTO<T>> {
    return request<DTO<T>>(`${this.baseURL}/${id}`, 'GET', params).then((res) => {
      return res.data;
    });
  }

  async getAll(params: object = {}): Promise<DTO<T>[]> {
    return request<DTO<T>[]>(this.baseURL, 'GET', params).then((res) => {
      return res.data;
    });
  }

  async create(data: DTO<T>): Promise<DTO<T>> {
    return request<DTO<T>>(this.baseURL, 'POST', data).then((res) => {
      return res.data;
    });
  }

  async replace(id: string | number, data: DTO<T>): Promise<void> {
    request<DTO<T>>(`${this.baseURL}/${id}`, 'PUT', data);
  }

  async update(id: string | number, data: Partial<DTO<T>>): Promise<void> {
    request<DTO<T>>(`${this.baseURL}/${id}`, 'PATCH', data);
  }

  async delete(id: string): Promise<void> {
    request<Count>(`${this.baseURL}/${id}`, 'DELETE', {});
  }
}

The resource object is instantiated with a baseURL, so for example setting the base URL to "/user" instantiates a wrapper for endpoints at "/user" routes. Each of the default functions correspond to GET, POST, PATCH, PUT, DELETE methods respectively that are created by default when you instantiate a boilerplate controller in Loopback.

Overriding Resource

This base class is meant as an abstract class for other classes to extend. For example the CustomerResource may look something like this

class CustomerResource extends Resource<Customer> {
  constructor() {
    super('/customers');
  }
  /*
    @override Resource.get
  */
  async get(id: string): Promise<DTO<IncludeAddress<IncludeUser<Customer>>>> {
    return request<DTO<IncludeAddress<IncludeUser<Customer>>>>(`${this.baseURL}/${id}`, 'GET', {}).then((res) => {
      return res.data;
    });
  }
  
  async getDefaultAddress(id: typeof Customer.prototype.id): Promise<DTO<Address> | null> {
    return request<DTO<Address>[]>(`${this.baseURL}/${id}/addresses`, 'GET', { where: { isDefault: true } }).then((res) => {
      return res.data.length > 0 ? res.data[0] : null;
    });
  }
}

We call the constructor for Resource using super, and can override default methods like get and add new methods like getDefaultAddress.

Last updated