Node Express 기반 RESTFul API 백엔드 서비스 프로젝트가 준비되었다면 해당 API 프로젝트에 Swagger를 적용해서 REST API 문서 자동화 및 테스트 환경을 구성하고 API 테스트를 Swaager UI 페이지를 통해 진행해보도록 하겠습니다. 

작업 순서는 크게 Node Express 개발 프로젝트 에 Swagger 기본 서비스 환경을 구성한후 기 개발된 C/R/U/D API 서비스에 Swagger주석을 통해 기능을 명세하고 주요 데이터 모델에 대해 Swagger Components기능을 통해 yaml파일을 이용 공통적으로 사용하는 데이터의 구조를 표현해 보겠습니다. 

1) Node Express 프로젝트 기반 Swagger 환경 구성하기

A) Express 프로젝트에 Swagger 지원 패키지 설치하기 

-하기 명령어를 통해 swagger-jsdoc,swagger-ui-express 두개의 swagger지원  개발용 패키지를 설치합니다.
-해당 패키지들은 개발 프로젝트안에 설치됩니다.

npm install swagger-jsdoc swagger-ui-express --save-dev

-설치가 완료되었다면 package.json파일내 devDependencies 영역에서 설치내역을 확인해봅니다.


B) swagger폴더 생성 및 swagger.js모듈 추가하기 

-프로젝트 루트에 swagger폴더를 생성하고 폴더내에 swagger.js 모듈파일을 추가합니다.
-swagger.js파일안애 options별 설정을 통해 swagger서비스 환경정보를 설정합니다.
-주요 기본 속성에 대해  설명합니다.

definition: Swagger 서비스 환경설정 주요 속성을 설정합니다.

ㄴopenapi: OAS 2.0 OR OAS 3.0 표준 사용 스펙 버전을 정의합니다.

ㄴinfo: API Document문서에 대한 제목/설명/제공자 정보를 설정합니다.

ㄴservers: API Document 명세를 서비스할 로컬/테스트/운영계별 서비스 주소를 정의합니다.

apis: 프로젝트내 실제 RESTFul API서비스를 제공하는 각종 모듈파일의 경로를 정의합니다.

const swaggerUi = require("swagger-ui-express");
const swaggerJsdoc = require("swagger-jsdoc");

const options = {
  definition: {
    openapi: "3.0.0",
    info: {
      title: "Sample Blog Api",
      description: "Sample Blog Web App RESTful API Documentation",
      contact: {
        name: "MixedCode",
        email: "contact@mixedcode.com",
        url: "https://mixedcode.com",
      },
      version: "1.0.0",
    },
    servers: [
      {
        url: "http://localhost:3000/",
        description: "Local Development",
      },
      {
        url: "http://test.co.kr/",
        description: "Test Server",
      },
      {
        url: "http://real.co.kr/",
        description: "Real Server",
      },
    ],
    // components: {},
  },
  apis: ["./routes/api/*.js", "./swagger/*"],
};

const specs = swaggerJsdoc(options);

module.exports = {
  swaggerUi,
  specs,
};


상기 속성외에도 많은 추가 속성들이 제공되며 자세한 내용들은 하기 링크정보를 참고하시면 됩니다. 

https://swagger.io/docs/specification/basic-structure/


C) app.js 파일내 Swagger설정 구성하기 

Step1) swagger 모듈을 app.js내에 참조합니다.

-하기 코드내 <노랑색>코드가 기존소스에 추가된 코드영역입니다.

--app.js 

var cookieParser = require("cookie-parser");
var logger = require("morgan");

//swagger 모듈 참조하기
const { swaggerUi, specs } = require("./swagger/swagger");

var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");


Step2) Swagger UI 웹페지 서비스 기본 주소 경로설정 및 기본 명세정보를 설정합니다.

app.use("/", indexRouter);
app.use("/users", usersRouter);

app.use("/api/blogs", blogAPIRouter);

//swagger 모듈 호출하기
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs));

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});



2) CRUD API 요청 유형별 Swagger 주석을 통한 기능별 스펙 정의하기

기본적인 Node Express 프로젝트에 Swagger 서비스 환경 구성을 마무리했다면 백엔드 개발자분들이 개발한 RESTFul API 기능들에 대한 Swagger 서비스 스펙을 주석형태로 상세히 기술하는 작업을 진행해보겠습니다.

A)전체 게시글 목록 정보 조회 API Swagger 스펙 정의하기
-Swagger 기능명세는 기본적으로  해당 API 라우터의 각각의 RESTful API 라우팅 메소드의 주소/호출방식에 따라 하기 주석 포맷형태로 Swagger 명세를 작성합니다.
-주석에 대한 간략한 소개를 작성해봅니다.
@openapi  : 스웨거를 위한 OPEN API 명세 작성 선언부분
'api/blogs/all'  : 호줄주소 명세
   get: 호출방식 명세(get,post,patch,put,delete...)
       tags: API 주소들을 업무별로 구분해 그룹핑해주는 명칭
         - Blog APIs :
API 주소들을 업무별로 구분해 그룹핑해주는 명칭
       summary : 해당 단일 API에 대한 기능요약
      request 전달 데이터 유형: requestBody(HTTP본문에 포함된 JSON값),parameters(URL을 통해 전달되는 데이터포맷)
      ㄴrequestBody : HTTP본문을 통해 전달되는 데이터 포맷 과 필수전달여부,데이터형식등을 정의합니다.
     
parameters : URL을 통해 전달되는 QueryString방식(in:query) 이나 Parameter방식(in:path)에 대한 값을 데이터 포맷과 전달방식을 정의합니다.
       response : 응답 상태 유형별 응답결과 및 결과 포맷 지정

/**
 * @openapi
 * '/api/blogs/all':
 *  get:
 *     tags:
 *     - Blog APIs
 *     summary: 전체 게시글 목록 정보 조회 API
 *     responses:
 *      200:
 *        description: Successfully
 *        content:
 *          application/json:
 *            schema:
 *              type: object
 *              properties:
 *                code:
 *                  type: integer
 *                data:
 *                  type: object
 *                msg:
 *                  type: string
 *      400:
 *        description: Bad Request
 *      404:
 *        description: Not Found
 *      500:
 *        description: Server Error
 */
router.get("/all", async (req, res, next) => {...});



B)단일 블로그 게시글 등록 API Swagger 스펙 정의하기
-프론트엔드로부터 HTTP본문을 통해 전달되는 JSON INPUT값을 scheme의 $ref :'데이터형식정의 yaml 파일경로' 를 통해 직접 구조를 기술하지 않고 swagger components를 통해 API에 전달되는 데이터 구조(스키마)를 정의한 예시입니다. 

/**
 * @openapi
 * '/api/blogs/':
 *  post:
 *     tags:
 *     - Blog APIs
 *     summary: 단일 블로그 게시글 등록 API
 *     requestBody:
 *       description: 단일 게시글 JSON 데이터
 *       required: true
 *       content:
 *        application/json:
 *          schema:
 *            $ref: '#/components/schemas/Blog'
 *     responses:
 *      200:
 *        description: Successfully
 *        content:
 *          application/json:
 *            schema:
 *              $ref: '#/components/schemas/APIResult'
 */
router.post("/", async (req, res, next) => { ... });



C)게시글번호 기준 단일 블로그 정보 조회-쿼리스트링 API Swagger 스펙 정의하기
-/api/blogs?id={id} 와 같이 호출 URL에 QueryString방식( ?키=값&키=값..) 형식으로 전달되는 API 요청방식에 대한  기능명세 예시입니다.
QueryString방식은 parameters 속성의 in속성을 query로 지정해야하며 필수여부,전달 데이터 형식을 명세해야하며 API주소내의 {id}값과 실제 API의 값을 추출하는
키값과 일치해야합니다.

/**
 * @openapi
 * '/api/blogs?id={id}':
 *  get:
 *     tags:
 *     - Blog APIs
 *     summary: 게시글번호 기준 단일 블로그 정보 조회 API
 *     parameters:
 *      - name: id
 *        in: query
 *        description: Blog Article Id
 *        required: true
 *        schema:
 *          type: integer
 *     responses:
 *      200:
 *        description: Successfully
 *        content:
 *          application/json:
 *            schema:
 *              type: object
 *              properties:
 *                code:
 *                  type: integer
 *                data:
 *                  type: object
 *                msg:
 *                  type: string
 *      400:
 *        description: Bad Request
 *      404:
 *        description: Not Found
 *      500:
 *        description: Server Error
 */
router.get("/", async (req, res, next) => { ....});



D)단일 블로그 게시글 수정 API Swagger 스펙 정의하기
-RESTFul API를 통해 데이터를 수정하는 경우는 put방식과 patch방식 두가지를 사용할수 있는데요.
-put방식은 수정하려는 테이블의 모든 컬럼값을 대상으로 수정하는 경우 호출하는 방식이고 patch는 해당 테이블의 특정 컬럼값만 수정하는 경우 호출하는 방식입니다.
즉, 게시글 테이블에 컬럼이 4개라면 id,title,contents,regist_date 4개 속성의 값을 모두 수정하는겨우 put를 사용하고 title,contents값만 수정한다면 patch를 이용한다고 이해하시면 됩니다.

/**
 * @openapi
 * '/api/blogs':
 *  patch:
 *     tags:
 *     - Blog APIs
 *     summary: 단일 블로그 게시글 수정 API
 *     requestBody:
 *       description: 수정 게시글 JSON 데이터
 *       required: true
 *       content:
 *        application/json:
 *          schema:
 *            $ref: '#/components/schemas/Blog'
 *     responses:
 *      200:
 *        description: Successfully
 *        content:
 *          application/json:
 *            schema:
 *              $ref: '#/components/schemas/APIResult'
 */
router.patch("/", async (req, res, next) => { .... });



E)게시글번호 기준 단일 블로그 정보 조회-파라메터방식 API Swagger 스펙 정의하기
-/api/blogs/{id} 와 같이 호출 URL 경로(Path)내에 파라메터 Path 방식(/api/blogs/1) 데이터를 전달하는 API 요청방식에 대한  기능명세 예시입니다.
파라메터 Path 방식은 parameters 속성의 in속성을 path로 지정해야하며 필수여부,전달 데이터 형식을 명세해야하며 API주소내의 {id}값과 실제 API의 값을 추출하는키값과 일치해야합니다.

/**
 * @openapi
 * '/api/blogs/{id}':
 *  get:
 *     tags:
 *     - Blog APIs
 *     summary: 게시글번호 기준 단일 블로그 정보 조회 API
 *     parameters:
 *      - name: id
 *        in: path
 *        description: Blog Article Id
 *        required: true
 *        schema:
 *          type: integer
 *     responses:
 *      200:
 *        description: Successfully
 *        content:
 *          application/json:
 *            schema:
 *              type: object
 *              properties:
 *                code:
 *                  type: integer
 *                data:
 *                  type: object
 *                msg:
 *                  type: string
 *      400:
 *        description: Bad Request
 *      404:
 *        description: Not Found
 *      500:
 *        description: Server Error
 */
router.get("/:id", async (req, res, next) => { ... });



F)단일 블로그 정보 삭제 API Swagger 스펙 정의하기


/**
 * @openapi
 * '/api/blogs/{id}':
 *  delete:
 *     tags:
 *     - Blog APIs
 *     summary: 단일 블로그 정보 삭제 API
 *     parameters:
 *      - name: id
 *        in: path
 *        description: Blog Article Id
 *        required: true
 *        schema:
 *          type: integer
 *     responses:
 *      200:
 *        description: Successfully
 *        content:
 *          application/json:
 *            schema:
 *              type: object
 *              properties:
 *                code:
 *                  type: integer
 *                data:
 *                  type: object
 *                msg:
 *                  type: string
 *      400:
 *        description: Bad Request
 *      404:
 *        description: Not Found
 *      500:
 *        description: Server Error
 */
router.delete("/:id", async (req, res, next) => { ... });


3) Swagger Component 사용법

RESTful API 기능 구현시 API를 호출하는 측에서 제공해주는 데이터들이 있습니다
주로 API 호출주소를 통해 /api/blogs?id=1 형식과 같이 쿼리스트링 방식으로 호출 주소를 통해 전달하거나 또는 /api/blogs/1 호출주소내에 데이터를 포함해 전달하는 파라메터 방식으로 전달하는 경우 또는 http body 본문내에 json데이터 형태로 데이터를 전달하는 방식이 대표적인데요.

이렇게 클라이언트에서 제공해주는 데이터의 형식과 기본값등을 설정 API기능의 Swagger주석방식을 통해 일일이 중복해서 개별적으로 정의도 할수 있지만 별도의 공통 파일 헝태로 정의하여 사용할수 방식을 Swagger Components라고 합니다.

자세한 사용법은 하기 docs링크를 참고바랍니다.
https://swagger.io/docs/specification/components/

Swagger Components 는 주로 특정 폴더/경로내에 ~.json 또는 ~.yaml파일로 정의가 가능하고 실제 API에서는 주석부분에서 $ref를 통해 경로를 지정하고 해당 파일을 참조하여 사용가능합니다.
$ref: '#/components/schemas/APIResult'


Swagger Components 파일들은 클라이언트에서 전달되는 데이터 구조도 정의 가능하지만 백엔드 서버에서 API를 호출하여 백엔드에서 제공되는 각종 데이터 구조(Schema)에 대해서도 그 형식 스펙을 정의할수도 있습니다. 

Swagger Components의 json,yaml파일로 정의된 데이터 구조(Schema)들은 Swagger UI 웹페이지 맨 하단에 Schema영역에서 일괄 설정 이름으로 해당 형식/스펙을 확인할수 있도록 제공됩니다.


Swagger Components 정의는 프로젝트 ROOT의 swagger 폴더내에 ~.yaml 또는 ~.json형식으로 정의가 가능하며 현재 프로젝트에서는 .yaml 형식으로 두개의 yaml파일을 제공하고 있습니다.

-apiResult.yaml : 현재 프로젝트는 어떤 API를 호출해도 동일한 형식의 결과값을 반환하고 있습니다.
해당 반환형식 구조에 대해 yaml파일로 정의한 파일입니다.

components:
  schemas:
    APIResult:
      properties:
        code:
          type: integer
          description: 호출결과코드
        data:
          type: object
          description: 반환JSON데이터
        msg:
          type: string
          description: 처리결과메시지


-blog.yaml : 게시글등록/수정시 클라이언트에서 전달되는 블로깅 게시글 데이터 기본형식을 정의한 yaml파일입니다.

components:
  schemas:
    Blog:
      properties:
        article_id:
          type: integer
          description: 게시글 고유 번호
        title:
          type: string
          description: 제목
          default: 제목입니다.
        contents:
          type: string
          description: 내용
        is_display_code:
          type: integer
          description: 게시여부
          default: 1