English article is here: openapi-fetch-gen – Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript - DEV Community
npm registryã«ãå ¬éããã¦ãã¾ãã
å¾ã£ã¦ä»¥ä¸ã®ããã«ãã¦ã³ãã¼ãå¯è½ã§ãã
npm install @moznion/openapi-fetch-gen
ããã¯ä½ / èæ¯
OpenAPI 3ã®ä»æ§ããopenapi-ts/openapi-typescriptã«ããçæãããã¹ãã¼ã (.d.ts) ãã¡ã¤ã«ããTypeScriptã®APIã¯ã©ã¤ã¢ã³ããèªåçæãããã¼ã«ã§ãã
openapi-ts/openapi-typescriptã¯ããªããã¯ãã«ãã¤å®æåº¦ãé«ããã¼ã«ã§ãæ¯è¼çè¤éãªOpenAPIã®å®ç¾©ããã§ã綺éºãªã¹ãã¼ããã¡ã¤ã«ãçæãããã¨ãã§ãã¾ãããªããã¤openapi-fetchã¨ããã©ã¤ãã©ãªãä½µãã¦æä¾ããã¦ããããããç¨ãããã¨ã§çæãããã¹ãã¼ããã¡ã¤ã«ã¨çµã¿åããã¦API Clientãtype-safeãªå½¢ã§ä½æãããã¨ãã§ãã¾ãã
ãã ããã«ã¯1ç¹èª²é¡ããããopenapi-fetchã¯ããã¾ã§ãã¹ãã¼ããèªã¿è¾¼ãã§API Clientãä½ããã©ã¤ãã©ãªãã§ãããAPIã¯ã©ã¤ã¢ã³ããèªåçæãããã®ã§ã¯ãªãããããåã¨ã³ããã¤ã³ãã«ã¤ãã¦APIã¯ã©ã¤ã¢ã³ããããã¥ã¢ã«ã§ä½æããå¿ è¦ãããã¾ããã
ã¨ãã課é¡ã解決ããããã«ãã®åº¦ä½æãããã®ããã®openapi-fetch-genã§ãã
ç¨æ³
以ä¸ã®ãããªOpenAPI 3ã®å®ç¾©YAMLãä¾ã«åãã¾ãã
schema.yaml (ã¡ãã£ã¨é·ãã§ãããã¾ãããããREST APIã®å®ç¾©ã ã¨æã£ã¦ãã ãã)
openapi: 3.0.3 info: title: Fictional Library User Management Service API version: 1.0.0 description: | A RESTful API for managing library user records and their loan information. servers: - url: https://api.fictionallibrary.example.com/v1 description: Production server paths: /users/{userId}: parameters: - $ref: '#/components/parameters/userId' - name: Authorization in: header required: true schema: type: string description: Authorization Header - name: Application-Version in: header required: true schema: type: string description: Application version - name: Something-Id in: header required: true schema: type: string description: Identifier of something get: summary: Get user details description: Retrieve detailed information for a specific user. responses: '200': description: User details content: application/json: schema: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' put: summary: Replace user description: Replace a user's entire record. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserUpdate' responses: '200': description: Updated user record content: application/json: schema: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' patch: summary: Update user fields description: Partially update a user's information. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserPatch' responses: '200': description: Updated user record content: application/json: schema: $ref: '#/components/schemas/User' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' delete: summary: Delete user description: Soft-delete a user record. responses: '204': description: User deleted (no content) '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' components: parameters: userId: name: userId in: path required: true description: Unique user identifier (UUID) schema: type: string format: uuid responses: BadRequest: description: Bad request due to invalid input content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Authentication required or failed content: application/json: schema: $ref: '#/components/schemas/Error' Forbidden: description: Insufficient permissions to access resource content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' Conflict: description: Conflict with current state of the resource content: application/json: schema: $ref: '#/components/schemas/Error' schemas: Error: type: object properties: code: type: integer description: HTTP status code message: type: string description: Error message detailing the cause required: - code - message User: type: object properties: id: type: string format: uuid name: type: string email: type: string format: email membershipType: type: string enum: [REGULAR, PREMIUM, STUDENT] registeredAt: type: string format: date-time address: $ref: '#/components/schemas/Address' required: - id - name - email - membershipType - registeredAt Address: type: object properties: postalCode: type: string street: type: string city: type: string country: type: string required: - street - city - country UserCreate: type: object properties: name: type: string email: type: string format: email membershipType: type: string enum: [REGULAR, PREMIUM, STUDENT] address: $ref: '#/components/schemas/Address' required: - name - email - membershipType UserUpdate: allOf: - $ref: '#/components/schemas/UserCreate' - type: object properties: id: type: string format: uuid required: - id UserPatch: type: object description: Schema for partial updates â include only fields to change properties: name: type: string email: type: string format: email membershipType: type: string enum: [REGULAR, PREMIUM, STUDENT] address: $ref: '#/components/schemas/Address'
ãã®æã«ä»¥ä¸ã®ãããªã³ãã³ããå®è¡ããã¨openapi-typescriptãç¨ãã¦ã¹ãã¼ããã¡ã¤ã« (.d.ts) ãçæããã¾ã:
openapi-typescript --output schema.d.ts ./schema.yaml
ãã®æã«çæãããã¹ãã¼ããã¡ã¤ã«ã¯ä»¥ä¸ã®ããã«ãªã£ã¦ãã¾ã
schema.d.ts
/** * This file was auto-generated by openapi-typescript. * Do not make direct changes to the file. */ export interface paths { "/users/{userId}": { parameters: { query?: never; header: { /** @description Authorization Header */ Authorization: string; /** @description Application version */ "Application-Version": string; /** @description Identifier of something */ "Something-Id": string; }; path: { /** @description Unique user identifier (UUID) */ userId: components["parameters"]["userId"]; }; cookie?: never; }; /** * Get user details * @description Retrieve detailed information for a specific user. */ get: { parameters: { query?: never; header: { /** @description Authorization Header */ Authorization: string; /** @description Application version */ "Application-Version": string; /** @description Identifier of something */ "Something-Id": string; }; path: { /** @description Unique user identifier (UUID) */ userId: components["parameters"]["userId"]; }; cookie?: never; }; requestBody?: never; responses: { /** @description User details */ 200: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["User"]; }; }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; 403: components["responses"]["Forbidden"]; 404: components["responses"]["NotFound"]; }; }; /** * Replace user * @description Replace a user's entire record. */ put: { parameters: { query?: never; header: { /** @description Authorization Header */ Authorization: string; /** @description Application version */ "Application-Version": string; /** @description Identifier of something */ "Something-Id": string; }; path: { /** @description Unique user identifier (UUID) */ userId: components["parameters"]["userId"]; }; cookie?: never; }; requestBody: { content: { "application/json": components["schemas"]["UserUpdate"]; }; }; responses: { /** @description Updated user record */ 200: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["User"]; }; }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; 403: components["responses"]["Forbidden"]; 404: components["responses"]["NotFound"]; }; }; post?: never; /** * Delete user * @description Soft-delete a user record. */ delete: { parameters: { query?: never; header: { /** @description Authorization Header */ Authorization: string; /** @description Application version */ "Application-Version": string; /** @description Identifier of something */ "Something-Id": string; }; path: { /** @description Unique user identifier (UUID) */ userId: components["parameters"]["userId"]; }; cookie?: never; }; requestBody?: never; responses: { /** @description User deleted (no content) */ 204: { headers: { [name: string]: unknown; }; content?: never; }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; 403: components["responses"]["Forbidden"]; 404: components["responses"]["NotFound"]; }; }; options?: never; head?: never; /** * Update user fields * @description Partially update a user's information. */ patch: { parameters: { query?: never; header: { /** @description Authorization Header */ Authorization: string; /** @description Application version */ "Application-Version": string; /** @description Identifier of something */ "Something-Id": string; }; path: { /** @description Unique user identifier (UUID) */ userId: components["parameters"]["userId"]; }; cookie?: never; }; requestBody: { content: { "application/json": components["schemas"]["UserPatch"]; }; }; responses: { /** @description Updated user record */ 200: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["User"]; }; }; 400: components["responses"]["BadRequest"]; 401: components["responses"]["Unauthorized"]; 403: components["responses"]["Forbidden"]; 404: components["responses"]["NotFound"]; }; }; trace?: never; }; } export type webhooks = Record<string, never>; export interface components { schemas: { Error: { /** @description HTTP status code */ code: number; /** @description Error message detailing the cause */ message: string; }; User: { /** Format: uuid */ id: string; name: string; /** Format: email */ email: string; /** @enum {string} */ membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; /** Format: date-time */ registeredAt: string; address?: components["schemas"]["Address"]; }; Address: { postalCode?: string; street: string; city: string; country: string; }; UserCreate: { name: string; /** Format: email */ email: string; /** @enum {string} */ membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; address?: components["schemas"]["Address"]; }; UserUpdate: components["schemas"]["UserCreate"] & { /** Format: uuid */ id: string; }; /** @description Schema for partial updates â include only fields to change */ UserPatch: { name?: string; /** Format: email */ email?: string; /** @enum {string} */ membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; address?: components["schemas"]["Address"]; }; }; responses: { /** @description Bad request due to invalid input */ BadRequest: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["Error"]; }; }; /** @description Authentication required or failed */ Unauthorized: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["Error"]; }; }; /** @description Insufficient permissions to access resource */ Forbidden: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["Error"]; }; }; /** @description Resource not found */ NotFound: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["Error"]; }; }; /** @description Conflict with current state of the resource */ Conflict: { headers: { [name: string]: unknown; }; content: { "application/json": components["schemas"]["Error"]; }; }; }; parameters: { /** @description Unique user identifier (UUID) */ userId: string; }; requestBodies: never; headers: never; pathItems: never; } export type $defs = Record<string, never>; export type operations = Record<string, never>;
ããã¾ã§ã¯ããã®çæãããã¹ãã¼ããã¡ã¤ã«ã¨openapi-fetchãä½µç¨ãã¦æã§APIã¯ã©ã¤ã¢ã³ããæ¸ãå¿ è¦ãããã¾ããããopenapi-fetch-genã使ãã¨ã¹ãã¼ããã¡ã¤ã«ãå ¥åã¨ãã¦ä»¥ä¸ã®ããã«å®è¡ãããã¨ã§APIã¯ã©ã¤ã¢ã³ããèªåçæãããã¨ãã§ãã¾ã:
openapi-fetch-gen --input ./schema.d.ts --output ./generated_client.ts
generated_client.tsã¯schema.d.tsãå
ã«èªåçæãããTypeScriptã¬ãã«ã§type-safeãªAPIã¯ã©ã¤ã¢ã³ãã§ããä¸é¨ãæ½åºããã¨ä»¥ä¸ã®ãããªã³ã¼ãã¨ãªãã¾ã:
import createClient, { type ClientOptions } from "openapi-fetch"; import type { paths } from "./schema"; // generated by openapi-typescript export class Client { private readonly client; constructor(clientOptions: ClientOptions) { this.client = createClient<paths>(clientOptions); } ... /** * Replace user */ async putUsersUserid( params: { header: { Authorization: string; "Application-Version": string; "Something-Id": string; }; path: { userId: string }; }, body: { name: string; email: string; membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; address?: { postalCode?: string; street: string; city: string; country: string; }; } & { id: string }, ) { return await this.client.PUT("/users/{userId}", { params, body, }); } ... }
FYI: çæãããAPIã¯ã©ã¤ã¢ã³ã (ãã«)
// THIS FILE IS AUTO-GENERATED BY openapi-fetch-gen. // DO NOT EDIT THIS FILE MANUALLY. // See Also: https://github.com/moznion/openapi-fetch-gen import createClient, { type ClientOptions } from "openapi-fetch"; import type { paths } from "./schema"; // generated by openapi-typescript export class Client { private readonly client; constructor(clientOptions: ClientOptions) { this.client = createClient<paths>(clientOptions); } /** * Get user details */ async getUsersUserid(params: { header: { Authorization: string; "Application-Version": string; "Something-Id": string; }; path: { userId: string }; }) { return await this.client.GET("/users/{userId}", { params, }); } /** * Replace user */ async putUsersUserid( params: { header: { Authorization: string; "Application-Version": string; "Something-Id": string; }; path: { userId: string }; }, body: { name: string; email: string; membershipType: "REGULAR" | "PREMIUM" | "STUDENT"; address?: { postalCode?: string; street: string; city: string; country: string; }; } & { id: string }, ) { return await this.client.PUT("/users/{userId}", { params, body, }); } /** * Delete user */ async deleteUsersUserid(params: { header: { Authorization: string; "Application-Version": string; "Something-Id": string; }; path: { userId: string }; }) { return await this.client.DELETE("/users/{userId}", { params, }); } /** * Update user fields */ async patchUsersUserid( params: { header: { Authorization: string; "Application-Version": string; "Something-Id": string; }; path: { userId: string }; }, body: { name?: string; email?: string; membershipType?: "REGULAR" | "PREMIUM" | "STUDENT"; address?: { postalCode?: string; street: string; city: string; country: string; }; }, ) { return await this.client.PATCH("/users/{userId}", { params, body, }); } }
ãªããªã便å©ãªã®ã§ã¯ãªãã§ããããã
ä¸èº«ã¨ãã¦ã¯ã©ããªã£ã¦ããã®ã
TypeScript Compiler APIãdsherret/ts-morphã©ã¤ãã©ãªãä»ãã¦å©ç¨ãããã¨ã§ d.ts ãã¡ã¤ã«ãè§£éãããã®å
å®¹ã»æ§é ã«åºãã¦APIã¯ã©ã¤ã¢ã³ãã³ã¼ããçæãã¦ãã¾ããã¾ããä¸è¨ã®ä¾ã®ããã«çæãããAPIã¯ã©ã¤ã¢ã³ãã®ã³ã¼ãã®ä¸ã§ã¯openapi-fetchãç¨ãããã¦ãã¾ãã
ã¾ã¨ã
ã¨ããããã§ãopenapi-typescriptãåºåããTypeScriptã®ã¹ãã¼ãããTypeScriptã®APIã¯ã©ã¤ã¢ã³ããèªåçæãããã¼ã«ã§ããopenapi-fetch-genã®ãç´¹ä»ã§ãããã©ãããå©ç¨ãã ããã
ã¨ããã§ãããã«å ã㦠"Default HTTP Header" æ©è½ã¨ãããã®ãç¨æãã¦ãããããã¯æ¬æ¥ã®çæã¯ã©ã¤ã¢ã³ãã³ã¼ããè¦æ±ãã¦ãããAPIã¨ã³ããã¤ã³ããå¼ã³åºãã¡ã½ããããããã«æç¤ºçã«HTTP Headerãæ¸¡ããªããã°ãªããªã *1ãã¨ãããã®ãçååãããã®ã§ããè¯ãããä¾ã¨ãã¦ã¯ã Authorization ããããªããã¯ã»ã¨ãã©ã®ã¨ã³ããã¤ã³ãã§å
±éãã¦æ¸¡ãå¿
è¦ããã£ãããã¾ãããããããã¹ã¦ã®APIã¡ã½ããå¼ã³åºãã¡ã½ããã§æ¸¡ããªããã°ãªããªãã¨ãªãã¨é¢åã§ããããã¨ããããã§openapi-fetch-genã§ã¯ã¸ã§ããªã¯ã¹ãæ´»ç¨ãããã¨ã§ããã©ã«ãã®ããããæ¸¡ããããã«ãã¦ãããå ãã¦ã¡ãã£ã¨ããTypeScriptã®åã®ãã¯ããã¯ãå
é¨çã«ä½¿ããã¨ã§ãããã©ã«ããããã¨ãã¦æå®ãããããããåAPIå¼ã³åºãã¡ã½ããã§çç¥ã§ããããã«ãã¦ãã¾ãã
èå³ã®ããæ¹ã¯Default HTTP Headersã®ã»ã¯ã·ã§ã³ããåç
§ãã ããã
*1:ä¸è¨ã®çæã³ã¼ãä¾ã«ãããã¡ã½ããã®å¼æ°ä¸ã®params.headerãããã«å½ããã¾ã