npm包@nll/api-codegen-ts使用教程

前言

随着前端技术的不断发展,前端已经不再是传统的展示型页面构建,而是在向着服务端的API接口服务开发越来越深入。这就需要前端开发人员具备一定的后端编程知识和技能。而随着前后端分离的逐渐成熟,我们经常需要与后端同步API接口的定义,以便前端能正确地调用后端提供的API。本文将介绍npm包@nll/api-codegen-ts的使用方法,以协助前端开发人员更加顺畅地进行API接口开发。

安装

在使用之前,我们需要先将 @nll/api-codegen-ts 安装到我们的项目中。安装方式有两种:

全局安装

在命令行中输入以下命令:

本地安装

在项目根目录下,打开命令行,输入以下命令:

使用

生成客户端代码

@nll/api-codegen-ts 可以通过Swagger或OpenAPI API规范生成客户端代码。 我们可以通过运行以下命令来生成:

其中https://petstore.swagger.io/v2/swagger.json是Swagger JSON API规范URL。生成的代码将被输出到当前目录下一个名为src/api的目录中。

生成的代码是基于TypeScript的,由以下部分组成:

使用生成的API

生成的代码包括了一系列远程API请求的封装,模型类定义,以及API使用的类型,你可以通过以下方式使用:

// 引入生成的API代码
import { DefaultApi } from './api';

// 接口实例化
const api = new DefaultApi();

// 调用接口
async function listPets() {
  try {
    const response = await api.listPets({ limit: 10 });
    console.log(response.data);
  } catch (error) {
    console.error(error);
  }
}

代码生成器配置

如果我们不想使用默认配置,可以通过运行以下命令来生成一个基本的配置文件:

生成出的基本配置文件如下:

{
  "input": {
    "type": "url",
    "value": ""
  },
  "output": {
    "targetDir": "src/api",
    "modelNameSuffix": "",
    "enumNameSuffix": "Enum",
    "modelModulePrefix": "",
    "enumModulePrefix": "",
    "useOptionsParameter": false
  },
  "options": {
    "generateUnionEnums": true,
    "useSafeNames": false,
    "modelPropertyNaming": "original",
    "generateModelMethods": true,
    "generateApiClasses": true,
    "useAliasAsType": true,
    "useSingleRequestParameter": false,
    "generateModels": true,
    "generateEnums": true,
    "generateUnionTypes": true,
    "generateRouteTypes": false,
    "nestJs": false,
    "koaJs": false,
    "prefixEnumWithModelName": false
  }
}

我们可以根据自己的需求修改配置文件,然后在运行时通过以下命令来指定配置文件:

示例代码

以PetStore API为例,以下是使用 @nll/api-codegen-ts 生成的代码:

/** Models **/
// Model: Category
export class Category {
  id!: number;
  name?: string;
}

// Model: Pet
export class Pet {
  id!: number;
  category?: Category;
  name: string;
  photoUrls: Array<string>;
  tags?: Array<Tag>;

  // 定义构造器
  constructor(data?: Partial<Pet>) {
    Object.assign(this, data);
  }
}

// Model: Tag
export class Tag {
  id!: number;
  name?: string;
}

/** APIs **/
// API: DefaultApi
export class DefaultApi {
  private basePath: string;
  private axios: AxiosInstance;

  // 定义构造器
  constructor(config?: ApiConfig) {
    this.basePath = config?.basePath ?? '';
    this.axios = axios.create(config);
  }

  // 获取所有宠物
  async listPets(queryParams?: ListPetsQueryParams, options?: ApiRequestOptions): Promise<ApiResponse<UnknownType>> {
    const path = `${this.basePath}/pet`;

    const requestConfig: AxiosRequestConfig = {
      method: 'get',
      url: path,
      params: queryParams ? buildUrlSearchParams(queryParams).toString() : undefined,
      responseType: 'json',
      ...options?.axiosConfig,
    };

    try {
      const response = await this.axios.request(requestConfig);
      return asApiResponse<UnknownType>(response);
    } catch (error) {
      return asApiErrorResponse(error);
    }
  }

  // 创建宠物
  async createPet(data: Pet, options?: ApiRequestOptions): Promise<ApiResponse<UnknownType>> {
    const path = `${this.basePath}/pet`;

    const requestConfig: AxiosRequestConfig = {
      method: 'post',
      url: path,
      data,
      responseType: 'json',
      ...options?.axiosConfig,
    };

    try {
      const response = await this.axios.request(requestConfig);
      return asApiResponse<UnknownType>(response);
    } catch (error) {
      return asApiErrorResponse(error);
    }
  }

  // 获取特定ID的宠物
  async getPetById(petId: number, options?: ApiRequestOptions): Promise<ApiResponse<UnknownType>> {
    const path = `${this.basePath}/pet/${petId}`;

    const requestConfig: AxiosRequestConfig = {
      method: 'get',
      url: path,
      responseType: 'json',
      ...options?.axiosConfig,
    };

    try {
      const response = await this.axios.request(requestConfig);
      return asApiResponse<UnknownType>(response);
    } catch (error) {
      return asApiErrorResponse(error);
    }
  }

  // 更新宠物
  async updatePet(data: Pet, options?: ApiRequestOptions): Promise<ApiResponse<UnknownType>> {
    const path = `${this.basePath}/pet`;

    const requestConfig: AxiosRequestConfig = {
      method: 'put',
      url: path,
      data,
      responseType: 'json',
      ...options?.axiosConfig,
    };

    try {
      const response = await this.axios.request(requestConfig);
      return asApiResponse<UnknownType>(response);
    } catch (error) {
      return asApiErrorResponse(error);
    }
  }

  // 删除宠物
  async deletePet(petId: number, apiKey?: string, options?: ApiRequestOptions): Promise<ApiResponse<UnknownType>> {
    const path = `${this.basePath}/pet/${petId}`;

    const requestConfig: AxiosRequestConfig = {
      method: 'delete',
      url: path,
      headers: {
        apiKey: apiKey,
        ...options?.axiosConfig?.headers,
      },
      responseType: 'json',
      ...options?.axiosConfig,
    };

    try {
      const response = await this.axios.request(requestConfig);
      return asApiResponse<UnknownType>(response);
    } catch (error) {
      return asApiErrorResponse(error);
    }
  }

  // 上传宠物照片
  async uploadPetImage(petId: number, additionalMetadata?: string, file?: Blob, options?: ApiRequestOptions): Promise<ApiResponse<UnknownType>> {
    const path = `${this.basePath}/pet/${petId}/uploadImage`;

    const requestConfig: AxiosRequestConfig = {
      method: 'post',
      url: path,
      headers: {
        solution: 'solution-example',
        ...options?.axiosConfig?.headers,
      },
      params: { additionalMetadata: additionalMetadata ?? '' },
      data: file,
      responseType: 'json',
      ...options?.axiosConfig,
    };

    try {
      const response = await this.axios.request(requestConfig);
      return asApiResponse<UnknownType>(response);
    } catch (error) {
      return asApiErrorResponse(error);
    }
  }
}

// ------ 分割 --------
// Helper: buildUrlSearchParams
function buildUrlSearchParams(params: { [key: string]: unknown }): URLSearchParams {
  const searchParams = new URLSearchParams();
  for (const key in params) {
    if (params[key] !== undefined) {
      searchParams.set(key, String(params[key]));
    }
  }
  return searchParams;
}

// ------ 分割 --------
// Helper: asApiResponse
function asApiResponse<T>(response: AxiosResponse<T>): ApiResponse<T> {
  const headers = response.headers;
  const status = response.status;
  const data = response.data;
  return { data, headers, status };
}

// ------ 分割 --------
// Helper: asApiErrorResponse
function asApiErrorResponse(error: AxiosError): ApiErrorResponse {
  // API调用失败时,我们需要从response中,将错误信息、错误状态、请求头信息等归集到ApiErrorResponse类型中。
  const headers = error.response?.headers;
  const status = error.response?.status ?? 0;
  const data = error.response?.data ?? {};
  const errorResponse: ApiErrorResponse = { error: { message: error.message }, headers, status };
  if (data) {
    try {
      Object.assign(errorResponse.error, { data: JSON.parse(data) });
    } catch (_) {
      Object.assign(errorResponse.error, { data });
    }
  }
  return errorResponse;
}

// ------ 分割 --------
// Types
export type UnknownType = Record<string, unknown>;

export interface ListPetsQueryParams {
  limit?: number;
}

export interface ApiConfig {
  basePath?: string;
  axiosConfig?: AxiosRequestConfig;
}

export interface ApiRequestOptions {
  axiosConfig?: AxiosRequestConfig;
}

export interface ApiResponse<T> {
  data: T;
  headers: Record<string, string>;
  status: number;
}

export interface ApiErrorResponse {
  error: {
    message: string;
    data?: any;
  };
  headers: Record<string, string>;
  status: number;
}

结论

在本文中,我们介绍了 @nll/api-codegen-ts 这个npm包的使用方法,其可以自动生成前端以及后端API的客户端代码、模型定义等。通过这个npm包,我们可以大大提高我们的开发效率,并且可以减少一些模板式的代码编写。同时,这个生成代码也极大地简化了前端与后端之间的接口开发。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/600673e2fb81d47349e53d81


纠错
反馈