본문 바로가기
node.js

swagger 로 api문서를 만들어보자

by 루에 2021. 12. 9.
반응형

restful api 서버를 만들 때 몇가지 고민이 있었는데, 우리가 만드는게 서버에서 api를 제공하는 단순 구조가 아니라

 

C++ binary api > napi 작성 후 .node 변환 > .node를 rest 로 작성

 

이라는, 몇가지 단계가 있었기 때문에 rest api의 검증이라던지, 추후에는 사용자에게도 배포를 할 것이기 때문에 문서를 어떻게 할지 고민이 되던 차에 swagger를 이용해 검증도 하고 사용자 배포용으로 쓰기로 결정

 

swagger에 대해 찾아보면서 너무나 다양한 케이스가 발견되었는데 그 중에서 아래와 같이 결정하였다.

1. swagger2.0 / swagger3.0(openAPI 3.0)

2. annotation(in code) / json / YAML

 

결정한 이유는 다음과 같다.

1. 2.0과 3.0의 문법과 구조가 많이 다른 편인데, 관리 측면에서 3.0이 나아보임

2. swagger를 처음 사용해보는 것이었기 때문에 2.0 문법에 익숙한 편이 아니었어서 선택의 문제였음

3. annotation 방식으로 적용을 해보니 주석의 양이 어마어마한데, 작성해야 될 api가 최소 400여개라서 완성 후 코드 수가 가늠되지 않음

4. YAML은 ""를 사용하지 않아 편리하긴 한데, 내게 익숙한 문법은 아니었음. json은 범용적으로 사용되기도 하므로 유지보수 측면에서도 json이 나아보임.

 

eg) annotation 지옥

/**
 * @swagger
 *  /monitoring-data':
 *    get:
 *      tags:
 *      - tag
 *      description: jog
 *      produces:
 *      - application/json
 *      security:
 *      - identifier: []
 *      parameters:
 *      - in: "body"
 *        name: "parameter1"
 *        required: true
 *      responses:
 *       200:
 *        description: jog success.
 */
router.get('/api/monitoring-data', api.getMonitoringData);

짧게 필요한 것만 넣었음에도 api 하나 당 많게는 몇십 줄을 작성해야 한다...

 

swagger2.0 vs openapi 3.0 비교 문서

https://medium.com/@tgtshanika/open-api-3-0-vs-swagger-2-0-94a80f121022

 

Open API 3.0 vs Swagger 2.0

Swagger is one of the largest and widely used open source framework for API developers to design, build, develop and consume REST APIs…

medium.com

 

설치 및 설정 방법

express 에서 지원하는 swagger 모듈을 설치하고 적용한다.

$ npm install swagger-ui-express

app.js에 아래와 같이 작성

const express = require('express');
const app = express();
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

혹은 라우터에 아래와 같이 작성

const router = require('express').Router();
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');

router.use('/api-docs', swaggerUi.serve);
router.get('/api-docs', swaggerUi.setup(swaggerDocument));

 

swagger-jsdoc 이란 모듈이 있는데, 코드 내에 옵션을 작성하는 방식이다. json으로 대체하기로 하였기 때문에 사용하지는 않았다.

 

 

작성방법

기본적인 구성

 

간략하게 보자면

1. 기본 정보

2. servers > 사용할 http통신규약(http, https 등)

3. tags > api 그룹

4. paths > api 작성

5. components >> schemas > 본 문서에서 참조하여 사용할 객체 작성

6. securitySchemes > 전역 객체 혹은 제한을 위한 값 작성

{
  "openapi": "3.0.1",
  "info": {
    "title": "타이틀",
    "description": "타이틀 아래 설명문",
    "termsOfService": "http://swagger.io/terms/",
    "contact": {
      "email": "apiteam@swagger.io"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    },
    "version": "1.0.0"
  },
  "externalDocs": {
    "description": "Find out more about Swagger",
    "url": "http://swagger.io"
  },
  // 2.0의 basePath를 대체하는 듯(확실하진 않음)
  "servers": [
    {
      "url": "https://petstore.swagger.io/v2"
    },
    {
      "url": "http://petstore.swagger.io/v2"
    }
  ],
  // api들을 그룹으로 묶을 카테고리 용도의 이름
  "tags": [
    {
      "name": "pet",
      "description": "Everything about your Pets",
      "externalDocs": {
        "description": "Find out more",
        "url": "http://swagger.io"
      }
    },
    {
      "name": "store",
      "description": "Access to Petstore orders"
    },
    {
      "name": "user",
      "description": "Operations about user",
      "externalDocs": {
        "description": "Find out more about our store",
        "url": "http://swagger.io"
      }
    }
  ],
  // api들을 작성할 곳
  "paths": {
  },
  // api에서 링크로 참조하여 사용할 객체를 선언한다. 2.0에선 definitions 라고 명명하던 것을 components로 변경
  "components": {
    "schemas": {
      // 참조할 이름
      "Order": {
        // 객체의 타입. string, integer, array, object 등등
        "type": "object",
        // 값의 속성을 여기에 기재
        "properties": {
          // 값의 이름 및 속성
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "petId": {
            "type": "integer",
            "format": "int64"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          // 이 곳 schemas에서 작성한 값들을 아래와 같은 형식으로 링크하여 사용
          "category": {
            "$ref": "#/components/schemas/Category"
          },
          "status": {
            "type": "string",
            // 값마다 설명을 작성해놓을 수 있다.
            "description": "Order Status",
            "enum": [
              "placed",
              "approved",
              "delivered"
            ]
          },
          "complete": {
            "type": "boolean",
            "default": false
          }
        },
        "xml": {
          "name": "Order"
        }
      }
    },
    // 2.0의 securitydefinitions를 대체한다. 전역으로 설정할 것들을 작성한다.
    "securitySchemes": {
      "petstore_auth": {
        "type": "oauth2",
        "flows": {
          "implicit": {
            "authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
            "scopes": {
              "write:pets": "modify pets in your account",
              "read:pets": "read your pets"
            }
          }
        }
      },
      "api_key": {
        "type": "apiKey",
        "name": "api_key",
        "in": "header"
      }
    }
  }
}

 

paths 안에 api 작성 예제

 

기본적인 구조

1. 접속 주소

2. 통신 메서드

3. 그룹명

4. 기본정보

5.  parameters에 body를 제외한 필요 정보(path, query 등)

6. requestBody > content 에 body에 들어갈 데이터 기재

7. responses 에 응답 관련 데이터 기재

8. security 에 전역 제한 사항(api key 등) 기재

// 주소
"/pet/{petId}": {
      // 통신 메서드
      "get": {
        // 그룹명
        "tags": [
          "pet"
        ],
        // 주소 옆에 설명 넣을 수 있음
        "summary": "Find pet by ID",
        "description": "Returns a single pet",
        "operationId": "getPetById",
        // 3.0에 와서 body는 따로 작성하나, 그 외 path나 query string은 여기에, 2.0의 문법처럼 작성
        "parameters": [
          {
            "name": "petId",
            "in": "path",
            "description": "ID of pet to return",
            "required": true,
            // 값의 속성을 작성할 때 schema안에 넣는게 3.0에서 좀 변화된 부분
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        // 응답에 대한 설정
        "responses": {
          "200": {
            "description": "successful operation",
            "content": {
              "application/xml": {
                "schema": {
                  "$ref": "#/components/schemas/Pet"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pet"
                }
              }
            }
          },
          "400": {
            "description": "Invalid ID supplied",
            "content": {}
          },
          "404": {
            "description": "Pet not found",
            "content": {}
          }
        },
        // 선언한 전역 객체로 제한 사항을 걸고자 하면 여기에 기재
        "security": [
          {
            "api_key": []
          }
        ]
      },
      "post": {
        "tags": [
          "pet"
        ],
        "summary": "Updates a pet in the store with form data",
        "operationId": "updatePetWithForm",
        "parameters": [
          {
            "name": "petId",
            "in": "path",
            "description": "ID of pet that needs to be updated",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        // post 요청처럼 body에 값을 설정할 때 새로 추가된 이 곳에 작성
        "requestBody": {
          "content": {
            // 데이터의 종류를 먼저 결정하고 schema > properties에 값들의 속성을 작성
            "application/x-www-form-urlencoded": {
              "schema": {
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Updated name of the pet"
                  },
                  "status": {
                    "type": "string",
                    "description": "Updated status of the pet"
                  }
                }
              }
            },
            // components > schemas 에 작성한 객체 참조
            "application/json" : {
              "schema": {
                "$ref": "#/components/schemas/이름"
              }
            },
            "application/xml" : {
              "schema": {
                "$ref": "#/components/schemas/이름"
              }
            }
          }
        },
        "responses": {
          "405": {
            "description": "Invalid input",
            "content": {}
          }
        },
        "security": [
          {
            "petstore_auth": [
              "write:pets",
              "read:pets"
            ]
          }
        ]
      },
      "delete": {
        "tags": [
          "pet"
        ],
        "summary": "Deletes a pet",
        "operationId": "deletePet",
        "parameters": [
          {
            "name": "api_key",
            "in": "header",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "petId",
            "in": "path",
            "description": "Pet id to delete",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Invalid ID supplied",
            "content": {}
          },
          "404": {
            "description": "Pet not found",
            "content": {}
          }
        },
        "security": [
          {
            "petstore_auth": [
              "write:pets",
              "read:pets"
            ]
          }
        ]
      }
    }

 

적용하고 나면 아래와 같은 화면을 볼 수 있다.

기본 정보 및 paths에 작성한 api 리스트
components > schemas 에 작성한 객체
Try it out 버튼으로 request를 보낸다.

 

반응형

댓글