[Node.js] Swagger openapi 3.0.0 연결하기

Server The SOPT·2022년 7월 22일
7

👾 작성자: 유지원
🐬 작성자의 한마디: 클라한테 이쁨받기? 쉽지!

1. Swagger 어떻게 사용해?

  1. 각 ts파일에 주석형식으로 추가하는 방법
  2. json 또는 yaml파일을 이용해 따로 관리하는 방법.

2. Swagger 설치 및 연결

0. 프로젝트 파일 구조

  • src/swagger 폴더를 생성
    • openapi.yaml, user.yaml 파일 존재

1. Install

npm install swagger-cli swagger-ui-express yamljs
npm install -D @types/swagger-ui-express @types/yamljs
  • swagger-cli : 쪼개진 yaml 파일들을 합칠 때 사용
  • swagger-ui-express : 작성해둔 API 명세서를 UI로 보여준다
  • yamljs : TS 파일로 yaml 파일을 읽어오기 위해 사용
    • 파일을 읽어오면 any타입으로 반환하기 때문에 타입 불일치 문제가 없다.

2. openapi.yaml 작성

  • 나중에는 밑에 api 문서 작성을 위한 내용을 쭉 추가할 것이다. 일단 이렇게만 적어놓고 기본적인 연결하러 가자
openapi: 3.0.0
info:
  version: 1.0.0
  title: SOPT API docs
  description: SOPT SERVER SEMINAR
  license:
    name: Jionee
servers:
  - url: http://localhost:8000/ #자신의 포트번호

#Paths api 경로 지정
paths: {}

3. index.ts 에서 연결하기

  • 최상위 index.ts (혹은 app.js 어쨌든 최상위 거기에 아래 코드 추가)
...
//swagger 연결
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
const path = require('path');
const swaggerSpec = YAML.load(path.join(__dirname, '../build/swagger.yaml'))
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
...

4. package.json 파일에 스크립트 추가

  • swagger-cli : 여기저기 퍼져있는 yaml 파일을 bundle 시켜준다.
    • ./src/swagger/openapi.yaml 을 포함해서 작성한 yaml파일들을 합쳐줌
      • (기타 파일들은 openapi.yml에 path로 작성될 것이므로 여기서 추가로 작성할 필요 없음)
    • 합쳐진 것을 build/swagger.yaml 파일로 내보내기
...
"scripts": {
  "build": "tsc && node dist",
  **"api-docs": "swagger-cli bundle ./src/swagger/openapi.yaml --outfile build/swagger.yaml --type yaml",
  "predev": "npm run api-docs",**
  "dev": "nodemon"
},
...

이제 터미널에 npm run dev 명령어를 치면,
→ api-docs를 predev로 dev 실행 전에 실행시켜줘서
→ swagger.yaml파일이 자동으로 생성된다.

화면에 뿌리는 것은 index.ts 파일에서 해줬으므로 이렇게만 설정해주면
http://localhost:8000/api-docs 에서 swagger 화면을 볼 수 있다.

Yeah~

3. API 명세 작성

명세 작성은 중복을 줄이기 위해
openapi.yaml에는 → 공통되는 부분을,
각 도메인별 yaml 에는 → 해당되는 부분을 작성한다.

예시를 보면서 익혀보자

1. openapi.yaml

1. components

  • schemas : 재사용할 수 있는 데이터 구조를 작성한다.
    • 여기서는 users, error를 작성했다.
  • examples: Request 혹은 Response에서 클라이언트가 편안하게 보려면 → “email”: “string” 보다는 “email”: “sopt@naver.com” 와 같은 형식으로 보여주는게 좋을 것이다.
components:
  parameters: null
  #Schemas : model 정보
  schemas:
    user:
      properties:
        name:
          type: string
          description: 유저 이름
        age:
          type: number
          description: 유저 나이
        phone:
          type: string
          description: 핸드폰번호
        email:
          type: string
          description: 이메일 주소
        school:
          properties:
            name:
              type: string
              description: 학교 이름
            major:
              type: string
              description: 전공
    Error:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
  #Examples
  examples:
    userExample:
      value:
        {
          "name": "홍길동",
          "phone": "01011112222",
          "email": "swaggerTest@naver.com",
          "school": {
            "name": "xx대학교",
            "major": "컴퓨터공학부"
          }
        }
  • responses: 응답 코드들은 재사용성이 강하다.
    • 예를 들어, 200번대 Response인데 data 없는 것, 400번대 BadRequest, 500번대 InternalServerError
  • $ref 의 경우 이미 선언된 애가 있으면 그걸 가져다 쓰는 느낌이다.
    • #은 루트 즉, openapi.yaml을 가리키는 것이라고 생각하자
    • 그래서 이따가도 나오겠지만 외부 파일에서 openapi.yaml을 참조할 때는 “./openapi.yaml/어쩌구저쩌구”의 형태로 참조한다.
#Responses 재사용되는 애들
  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: 서버 내부 오류

2. paths : 마치 라우터 같은 느낌 (아니긴하다)

  • yaml 파일이 여기저기 흩어져있으니까 그걸 찾아갈 수 있게 경로를 지정해줌
    • ~1 이런 문법은 공식 문서에서 친절하게 설명해놓았다.
#Paths api 경로 지정
paths:
  /user:
    $ref: './user.yaml#/~1user'
  /user/{userId}:
    $ref: './user.yaml#/~1user~1{userId}'

openapi.yaml 완성본

openapi: 3.0.0
info:
  version: 1.0.0
  title: SOPT API docs
  description: SOPT SERVER SEMINAR
  license:
    name: Jionee
servers:
  - url: http://localhost:8000/
components:
  parameters: null
  #Schemas : model 정보
  schemas:
    user:
      properties:
        name:
          type: string
          description: 유저 이름
        age:
          type: number
          description: 유저 나이
        phone:
          type: string
          description: 핸드폰번호
        email:
          type: string
          description: 이메일 주소
        school:
          properties:
            name:
              type: string
              description: 학교 이름
            major:
              type: string
              description: 전공
    Error:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
  #Examples
  examples:
    userExample:
      value:
        {
          "name": "홍길동",
          "phone": "01011112222",
          "email": "swaggerTest@naver.com",
          "school": {
            "name": "xx대학교",
            "major": "컴퓨터공학부"
          }
        }
  #Responses 재사용되는 애들
  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: 서버 내부 오류
#Paths api 경로 지정
paths:
  /user:
    $ref: './user.yaml#/~1user'
  /user/{userId}:
    $ref: './user.yaml#/~1user~1{userId}'

2. user.yaml을 작성해보자

1. 유저 생성 POST /user

  • tags : Swagger문서에서 User 카테고리로 묶어준다.
  • summary : 간단한 설명
  • consumes : json 쓸거야
  • requestBody : requsetBody 쓸거야
    • content 도 json이야
      • schema 는 user랑 같아 (openapi.yaml의 user 스키마를 참조해서 사용)
      • examples 도 openapi.yaml의 example을 참조해서 사용
    • responses
      • 200이면 데이터는 없는 성공 응답 보낼거야
      • 400이면 BadReqeust 보낼거야
      • 500이면 InternalServerError 보낼거야
/user:
  post:
    tags:
      - User
    summary: 유저 생성
    consumes:
      - application/json
    requestBody:
      x-name: body
      required: true
      content:
        application/json:
          schema:
            $ref: './openapi.yaml#/components/schemas/user'
          examples:
            userExample:
              $ref: './openapi.yaml#/components/examples/userExample'
    responses:
      '200':
        $ref: './openapi.yaml#/components/responses/successResponse'
      '400':
        $ref: './openapi.yaml#/components/responses/BadRequest'
      '500':
        $ref: './openapi.yaml#/components/responses/InternalServerError'

2. 유저 조회 GET /user/{userId}

  • parameters: request를 파라미터로 받을거야, 그거 이름은 userId고, 꼭 필요하고(required), 설명은 유저 id야. schema는 type string이야
  • responses
    • 200의 경우 별도의 data를 담아 보낼거야
    • 그 예시는 example과 같아
/user/{userId}:
  get:
    tags:
      - User
    summary: 유저 정보 조회
    parameters:
      - in: path
        name: userId
        required: true
        description: 유저 id
        schema:
          type: string
    responses:
      '200':
        description: 유저 조회 성공
        content:
          application/json:
            schema:
              type: object
              example: {
              "status": 200,
                "success": true,
                "message": "유저 조회 성공",
                "data": {
                  "school": {
                    "name": "xx대학교",
                    "major": "컴퓨터공학부"
                  },
                  "_id": "6283a755c2f673e5e81a6fea",
                  "name": "홍길동",
                  "phone": "01011112222",
                  "email": "validateTest-email2@naver.com",
                  "age": 20
                }
              }
      '400':
        $ref: './openapi.yaml#/components/responses/BadRequest'
      '500':
        $ref: './openapi.yaml#/components/responses/InternalServerError'

3. 유저 업데이트 PUT /user/{userId}

4. 유저 삭제 DELETE /user/{userID}

  • 업데이트와 삭제의 경우에는 response를 보내지 않았기 때문에 response 부분을 작성하지 않았어

user.yaml 완성본

/user:
  post:
    tags:
      - User
    summary: 유저 생성
    consumes:
      - application/json
    requestBody:
      x-name: body
      required: true
      content:
        application/json:
          schema:
            $ref: './openapi.yaml#/components/schemas/user'
          examples:
            userExample:
              $ref: './openapi.yaml#/components/examples/userExample'
    responses:
      '200':
        $ref: './openapi.yaml#/components/responses/successResponse'
      '400':
        $ref: './openapi.yaml#/components/responses/BadRequest'
      '500':
        $ref: './openapi.yaml#/components/responses/InternalServerError'

/user/{userId}:
  get:
    tags:
      - User
    summary: 유저 정보 조회
    parameters:
      - in: path
        name: userId
        required: true
        description: 유저 id
        schema:
          type: string
    responses:
      '200':
        description: 유저 조회 성공
        content:
          application/json:
            schema:
              type: object
              example: {
              "status": 200,
                "success": true,
                "message": "유저 조회 성공",
                "data": {
                  "school": {
                    "name": "xx대학교",
                    "major": "컴퓨터공학부"
                  },
                  "_id": "6283a755c2f673e5e81a6fea",
                  "name": "홍길동",
                  "phone": "01011112222",
                  "email": "validateTest-email2@naver.com",
                  "age": 20
                }
              }
      '400':
        $ref: './openapi.yaml#/components/responses/BadRequest'
      '500':
        $ref: './openapi.yaml#/components/responses/InternalServerError'
  put:
    tags:
      - User
    summary: 유저 정보 수정
    parameters:
      - in: path
        name: userId
        required: true
        description: 유저 id
        schema:
          type: string
    consumes:
      - application/json
    requestBody:
      description: \[Optional\] name, age, phone, email, school-name, school-major
      x-name: body
      required: true
      content:
        application/json:
          schema:
            $ref: './openapi.yaml#/components/schemas/user'

  delete:
    tags:
      - User
    summary: 유저 삭제
    parameters:
      - in: path
        name: userId
        required: true
        description: 유저 id
        schema:
          type: string

클라이언트한테 이쁨받기 성공





4. Swagger 공식문서

Basic Structure


참고

https://airplane9876.tistory.com/14yaml 파일 나누기 짱짱맨
https://velog.io/@hyex/Node.js-TS-프로젝트에-swagger-적용하기-Feat.-파일-분리파일분리22
https://velog.io/@kyy00n/Node.js-Swagger-개요-적용
https://llshl.tistory.com/49auto-gen이라는 라이브러리도 있던데 사용해보니까 어차피 얘도 수동이더라구요..?
https://any-ting.tistory.com/105주석을 사용해서 지양하는 것이 좋을 듯한데, yaml 형태 같은 것은 참고해도 좋을 듯 합니다

오류

  1. 자꾸 openapi.yaml이 validate하지 않는다고 한다면?

    → 무언가가 빠졌을 확률 있음, 아래의 openAPI.tootls 에서 검증하기

    (저는 이거 썼어용)

    OpenAPI.Tools

  2. Error: No default engine was specified and no extension was provided

    res.render 쓰지 말구 res.json으로 바꿔버리잣

    res.json({
      message: err.message,
      error: err
    });
profile
대학생연합 IT벤처창업 동아리 SOPT 30기 SERVER 파트 기술 블로그입니다.

0개의 댓글