L
L
Leonardo-lavanda2021-04-27 19:35:52
OOP
Leonardo-lavanda, 2021-04-27 19:35:52

How to describe a generic response from a restful api server?

I started learning typescript, for practice I decided to write a class for creating api entities. this is what the base class looks like.

export type ApiResponse =
  | { errors: Array<{ [key: string]: string }> }
  | { data: any }
  | { text: string };

type RequestData = {
  url?: string;
  method?: string;
  data?: any;
};

abstract class Api {
  protected readonly baseUrl: string;
  protected readonly endPoint: string;

  protected get url(): string {
    return new URL(this.endPoint, this.baseUrl).toString();
  }

  protected constructor(baseUrl = '/', endPoint: string) {
    this.baseUrl = baseUrl;
    this.endPoint = endPoint;
  }

  protected async http({
    url = '',
    method = 'GET',
    data = undefined,
  }: RequestData): Promise<ApiResponse> {
    try {
      const response: Response = await fetch(
        (new URL(url, this.url).toString()),
        { method, body: data }
      );
      return await response.json();
    } catch (e) {
      console.error(e.message);
      return { text: e.message };
    } finally {
      // finnally
    }
  }
}


How everything should work in general: I create an api entity, inheriting from the Api class, for example, I create a Post entity from Api.
What is my stupor: I have a method for an http request, which, depending on different situations, should return either an array with errors, or an object with data, or a request error. Naturally, when designing a base class, it is impossible to predict what might come in the request in the data field, do I have the right to put the type any in the data field in this case ?
export type ApiResponse =
  | { errors: Array<{ [key: string]: string }> }
  | { data: Array<any> }
  | { text: string };


In the future, I will clarify this same answer by creating an entity:

import Api, { ApiResponse } from "@/api/Api";


export type Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

class ApiPost extends Api {
  constructor(baseUrl: string, endPoint: string) {
    super(baseUrl, endPoint);
  }

  async list(count?: number): Promise<boolean | Array<Post>> {
    const response: ApiResponse = await this.http({
      url: `${count ? `?_limit=${count}` : ''}`,
    });

    if (Array.isArray(response))
      return response as Array<IPost>;

    return false;
  }
}

export default ApiPost;

Answer the question

In order to leave comments, you need to log in

1 answer(s)
D
Dmitry Belyaev, 2021-04-27
@Leonardo-lavanda

Use generic parameters

export type ApiResponse<T> =
  | { errors: Array<{ [key: string]: string }> }
  | { data: Array<T> }
  | { text: string };

abstract class Api<T> {
  protected readonly baseUrl: string;
  protected readonly endPoint: string;

  protected get url(): string {
    return new URL(this.endPoint, this.baseUrl).toString();
  }

  protected constructor(baseUrl = '/', endPoint: string) {
    this.baseUrl = baseUrl;
    this.endPoint = endPoint;
  }

  protected async http({
    url = '',
    method = 'GET',
    data = undefined,
  }: RequestData): Promise<ApiResponse<T>> {
    try {
      const response: Response = await fetch(
        (new URL(url, this.url).toString()),
        { method, body: data }
      );
      return await response.json();
    } catch (e) {
      console.error(e.message);
      return { text: e.message };
    } finally {
      // finnally
    }
  }
}

class ApiPost extends Api<Post> {
  constructor(baseUrl: string, endPoint: string) {
    super(baseUrl, endPoint);
  }

  async list(count?: number): Promise<false | Array<Post>> {
    const response: ApiResponse = await this.http({
      url: `${count ? `?_limit=${count}` : ''}`,
    });

    if (Array.isArray(response))
      return response;

    return false;
  }
}

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question