openapi-generator
OpenAPI Specification (OAS)는 RESTful API를 정의하고 문서화하기 위한 표준 형식입니다. 이 사양은 API의 구조, 동작, 요청 및 응답 형식 등을 기술하는 데 사용됩니다. OpenAPI는 JSON 또는 YAML 형식으로 작성되며, 다음과 같은 주요 요소를 포함합니다:
- servers: API가 배포된 URL 및 관련 정보.
- paths: API의 각 엔드포인트를 정의합니다. 각 경로는 HTTP 메서드(예: GET, POST)와 함께 요청할 수 있는 자원에 대한 정보를 포함합니다.
- components/responses: 각 경로에 대해 요청 형식(매개변수, 본문)과 응답 형식(상태 코드, 본문)의 세부 사항을 제공합니다.
- components/schemas: 요청 및 응답 데이터의 구조를 정의합니다. 이를 통해 클라이언트와 서버 간의 데이터 형식을 명확하게 이해할 수 있습니다.
- info: API의 버전, 제목, 설명 등과 같은 추가 정보를 포함합니다.
내가 해볼 것은 위에서 언급한 spec을 openapi-generator를 가지고 사용할 수 있는 소스코드로 변경해볼 것이다. 즉 API 요청을하는 소스코드로 만든다는 것이다.
generator는 typescript-axios를 사용해볼 것이다.
정리를 해보자면
STEP
- spec을 준비
- code generate
- generate된 소스코드를 사용해서 RESTful 통신
STEP 1
code generate을 하기위한 spec을 준비한다. 나는 jsonplaceholderAPI spec을 준비했다.
openapi: 3.0.1
info:
title: Example API
description: API for managing users
version: 1.0.0
servers:
- url: <https://jsonplaceholder.typicode.com>
paths:
/users:
get:
summary: Get list of users
operationId: getUsers
responses:
'200':
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
post:
summary: Create a new user
operationId: createUser
requestBody:
description: New user data
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewUser'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
/users/{id}:
get:
summary: Get user by ID
operationId: getUserById
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: User data
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalServerError'
components:
schemas:
User:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: John Doe
email:
type: string
example: johndoe@example.com
NewUser:
type: object
required:
- name
- email
properties:
name:
type: string
example: Jane Doe
email:
type: string
example: janedoe@example.com
Error:
type: object
properties:
success:
type: boolean
message:
type: string
responses:
successResponse:
description: successful request with no data
content:
application/json:
schema:
type: object
example: {"status": 200, "success": true, "message": "message"}
BadRequest:
description: 잘못된 요청
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
success: false
message: 잘못된 요청
InternalServerError:
description: 서버 에러
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
success: false
message: 서버 내부 오류
STEP 2
Spec을 정의해봤다. 그러면 정의된 Spec을 가지고 이번에는 generator을 사용해서 소스코드로 바꿔줄 것이다.
아래의 명령어를 사용해서 openapi-generator-cli 를 설치해준다.
npm install -g @openapitools/openapi-generator-cli
설치가 됐다면 아까 정의한 spec을 소스코드로 변경해줄 것이다. 명령어는 아래와 같다.
openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o ./src/api
generate
- i → spec file
- g → generator name
- o → output directory
명령어를 실행하면 아래와 같이 generate될 것이다.
📦api
┣ 📂.openapi-generator
┃ ┣ 📜FILES
┃ ┗ 📜VERSION
┣ 📜.gitignore
┣ 📜.npmignore
┣ 📜.openapi-generator-ignore
┣ 📜api.ts
┣ 📜base.ts
┣ 📜common.ts
┣ 📜configuration.ts
┣ 📜git_push.sh
┗ 📜index.ts
generate되고 나면 아래처럼 소스코드를 생성해준다. 그러면 우리는 path에서 정의한 대로 (operationId 를 참조) getUsers도 찾아볼수있다.
/* tslint:disable */
/* eslint-disable */
/**
* Example API
* API for managing users
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (<https://openapi-generator.tech>).
* <https://openapi-generator.tech>
* Do not edit the class manually.
*/
import type { Configuration } from './configuration';
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
import type { RequestArgs } from './base';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base';
/**
*
* @export
* @interface ModelError
*/
export interface ModelError {
/**
*
* @type {boolean}
* @memberof ModelError
*/
'success'?: boolean;
/**
*
* @type {string}
* @memberof ModelError
*/
'message'?: string;
}
/**
*
* @export
* @interface NewUser
*/
export interface NewUser {
/**
*
* @type {string}
* @memberof NewUser
*/
'name': string;
/**
*
* @type {string}
* @memberof NewUser
*/
'email': string;
}
/**
*
* @export
* @interface User
*/
export interface User {
/**
*
* @type {number}
* @memberof User
*/
'id'?: number;
/**
*
* @type {string}
* @memberof User
*/
'name'?: string;
/**
*
* @type {string}
* @memberof User
*/
'email'?: string;
}
/**
* DefaultApi - axios parameter creator
* @export
*/
export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @summary Create a new user
* @param {NewUser} newUser New user data
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createUser: async (newUser: NewUser, options: RawAxiosRequestConfig = {}): Promise => {
// verify required parameter 'newUser' is not null or undefined
assertParamExists('createUser', 'newUser', newUser)
const localVarPath = `/users`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(newUser, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary Get user by ID
* @param {number} id
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUserById: async (id: number, options: RawAxiosRequestConfig = {}): Promise => {
// verify required parameter 'id' is not null or undefined
assertParamExists('getUserById', 'id', id)
const localVarPath = `/users/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary Get list of users
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUsers: async (options: RawAxiosRequestConfig = {}): Promise => {
const localVarPath = `/users`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* DefaultApi - functional programming interface
* @export
*/
export const DefaultApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
return {
/**
*
* @summary Create a new user
* @param {NewUser} newUser New user data
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createUser(newUser: NewUser, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createUser(newUser, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DefaultApi.createUser']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
*
* @summary Get user by ID
* @param {number} id
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getUserById(id: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getUserById(id, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DefaultApi.getUserById']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
*
* @summary Get list of users
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getUsers(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<array>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getUsers(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DefaultApi.getUsers']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
/**
* DefaultApi - factory interface
* @export
*/
export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = DefaultApiFp(configuration)
return {
/**
*
* @summary Create a new user
* @param {NewUser} newUser New user data
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createUser(newUser: NewUser, options?: RawAxiosRequestConfig): AxiosPromise {
return localVarFp.createUser(newUser, options).then((request) => request(axios, basePath));
},
/**
*
* @summary Get user by ID
* @param {number} id
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUserById(id: number, options?: RawAxiosRequestConfig): AxiosPromise {
return localVarFp.getUserById(id, options).then((request) => request(axios, basePath));
},
/**
*
* @summary Get list of users
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUsers(options?: RawAxiosRequestConfig): AxiosPromise<array> {
return localVarFp.getUsers(options).then((request) => request(axios, basePath));
},
};
};
/**
* DefaultApi - object-oriented interface
* @export
* @class DefaultApi
* @extends {BaseAPI}
*/
export class DefaultApi extends BaseAPI {
/**
*
* @summary Create a new user
* @param {NewUser} newUser New user data
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public createUser(newUser: NewUser, options?: RawAxiosRequestConfig) {
return DefaultApiFp(this.configuration).createUser(newUser, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary Get user by ID
* @param {number} id
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public getUserById(id: number, options?: RawAxiosRequestConfig) {
return DefaultApiFp(this.configuration).getUserById(id, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary Get list of users
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public getUsers(options?: RawAxiosRequestConfig) {
return DefaultApiFp(this.configuration).getUsers(options).then((request) => request(this.axios, this.basePath));
}
}
</array</array
STEP 3
소스코드로 변환하기전에 우리는 generate된 소스코드를 가져다 사용하는 소스코드를 작성해야된다. 간단하게 아래처럼 작성해보았다.
//main.ts
import { Configuration, DefaultApi } from './src/api';
const config = new Configuration({
basePath: '<https://jsonplaceholder.typicode.com>', // Set the base URL here
});
const usersApi = new DefaultApi(config);
async function fetchUsers() {
try {
const response = await usersApi.getUsers(); // Method name depends on your OpenAPI spec
console.log(response.data);
} catch (error) {
console.error('Error fetching users:', error);
}
}
async function createUser() {
const newUser = {
id: 0,
name: 'New User',
email: 'newuser@example.com',
};
try {
const response = await usersApi.createUser(newUser); // Adjust as per your spec
console.log(response.data);
} catch (error) {
console.error('Error creating user:', error);
}
}
// Call the functions
fetchUsers();
createUser();
실행 시켜보면 아래처럼 결과가 나온다.
[
{
id: 1,
name: 'Leanne Graham',
username: 'Bret',
email: 'Sincere@april.biz',
address: {
street: 'Kulas Light',
suite: 'Apt. 556',
city: 'Gwenborough',
zipcode: '92998-3874',
geo: [Object]
},
phone: '1-770-736-8031 x56442',
website: 'hildegard.org',
company: {
name: 'Romaguera-Crona',
catchPhrase: 'Multi-layered client-server neural-net',
bs: 'harness real-time e-markets'
}
},
...
swagger-ui
swagger-ui를 사용해서 정의한 spec을 ui로 뽑아보는 작업도 진행해 보겠다.
프로젝트생성
node express를 사용해서ui를 띄어보도록 하겠다. 아래의 명령어를 사용해서 npm project를 만들어준다
npm init
프로젝트를 만든후에는 npm에서 모듈을 다운받아야 된다 아래의 명령어를 사용해서 다운받으면 된다.
npm i express swagger-ui-express yamljs
설치가 되었다면 아래의 소스코드를 사용해서 파일을 생성해주고 node명령어로 실행시켜주면된다.
const express = require('express');
const app = express();
const port = 3000;
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const path = require('path');
const swaggerSpec = YAML.load(path.join(__dirname, '../openapi.yaml'))
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// 서버 시작
app.listen(port, () => {
console.log(`Server is running at <http://localhost>:${port}`);
});
결과물
'🧑💻 실무 개발 & 시스템' 카테고리의 다른 글
AWS API Gateway에서 JWT 인증을 이용한 사용자 접근 제어 설정하기 (4) | 2024.11.22 |
---|---|
Docker Compose로 Nest.js와 MySQL을 사용한 애플리케이션 구축하기 (4) | 2024.11.14 |
GitAction을 활용한 OpenAPI Spec 자동 배포 및 Swagger UI 업데이트 과정(2) (0) | 2024.11.11 |
GitAction을 활용한 OpenAPI Spec 자동 배포 및 Swagger UI 업데이트 과정(1) (2) | 2024.11.08 |
[git] git 브랜치 클론해오는 법 (3) | 2024.10.14 |