보통은 서비스를 본격적으로 구현하기 위해 REST API들을 먼저 설계하는 것이 사용자 입장에서 요구사항을 제대로 이해할 수 있다. 이는 개발 목표를 정확하게 정해 두고 개발을 시작할 수 있어 협업을 하면서 도움이 많이 된다. (Not code first, Design first) 그러므로 시간을 들여서라도 데이터 스키마를 먼저 정의하고 필요한 리소스를 접근하기 위한 API들을 사전에 정의해두는 것이 좋은데, 이때 유용한 것이 Open API Specification과 Swagger Tool을 이용하는 것이다. 이를 이용하면 어플리케이션 수준에서 별도의 코드 작성없이도 라우팅과 validation을 지원해주기도 하지만 이러한 기능은 이번 프로젝트에서는 사용하지 않고 문서화를 위해 채택하였다. Open API를 작성하기 위한 구현 방법은 해당 페이지를 참고하였다.

Before demo 프로젝트 내부의 api 폴더에 open.yaml 파일을 만들었다. JSON으로 작성하는 것보다 yaml로 작성해서 JSON으로 변환하는 것이 가독성 측면에서 좋다. 또한 별도의 url을 통해 UI적으로 요청 및 응답 스키마, 요청별 발생할 수 있는 상태코드, 그룹화된 태그 등을 시각적으로 확인할 필요가 있다. 따라서 ‘yamljs’와 ‘swagger-ui-express’를 프로젝트에 추가해주었다.

npm install yamljs swagger-ui-express

아래는 Swagger Editor을 이용해 open api 문서를 코드로 작성해준 결과이다. 크게 openapi(version), info, server, tags, path, components로 구성되어 있다. 어떻게 쓰였는지를 이해하기 위해 코드의 일부 중요한 부분을 살펴보도록 하자.

tags:
  - name: user
    description: Methods to access and manage users
  - name: bugs
    description: Methods to access bug information and add a survey result
  - name: companies
    description: Methods to access company information and manage company interests and reservation
  - name: products
    description: Methods to access product information and manage product interests

이용가능한 API들은 크게 네 범주로 나뉜다. 접근하는 리소스 종류에 따라 user, bugs, companies, products로 구분하였다. 이를 통해 어플리케이션이 제공하는 메인 서비스들을 쉽게 확인할 수 있다.

paths:
  /user/signup:
    post:
      tags:
        - user
      summary: Signs up a user to the Debugging service
      description: Creates a user account for the given user details.
      operationId: signup
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserRegistration'
        required: true
      responses:
        '201':
          description: Account Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserAuthenticationSuccess'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorApiResponse'
        '409':
          description: User already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorApiResponse'

  /user/login:
    post:
      tags:
        - user
      summary: Logs in a user to the Debugging service
      description: Verify if a logged-in user is valid or not.
      operationId: login
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserLogin'
        required: true
      responses:
        '200':
          description: Login Succeeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserLogin'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorApiResponse'

단순성을 위해 회원 가입과 로그인 기능과 관련된 코드만 가져왔다. 각각의 메소드 별로 operationId를 정의하는 것은 해당 메소드명을 가진 함수를 invoke하여 라우팅을 할 수 있다. 그러나 이 기능은 사용하지 않고 직접 프로그래밍을 해서 작성하였다.

components:
  schemas:
    UserRegistration:
      type: object
      properties:
        username:
          type: string
        password:
          type: string
          format: password
          minLength: 5
        name:
          type: string
        contactNumbers:
          type: string
        email:
          type: string
          format: email
        address:
          type: string
          nullable: true
        sizeOfHouse:
          type: number
          nullable: true
        numOfRooms:
          type: integer
          nullable: true
      required:
        - username
        - password
        - name
        - contactNumbers
        - email
      example:
        username: 김다솔
        password: abc1234
        name: Dasol Kim
        contactNumbers: 010-1111-2222
        email: sorikiki98@sookmyung.ac.kr
    UserAuthenticationSuccess:
      type: object
      properties:
        token:
          type: string
        username:
          type: string
      required:
        - token
        - username
      example:
        username: 김다솔
    UserLogin:
      type: object
      properties:
        username:
          type: string
        password:
          type: string
          format: password
          minLength: 5
      required:
        - username
        - password
      example:
        username: 김다솔
        password: abc1234

components 섹션에 회원가입 시 body에 들어갈 UserRegistration과 로그인 시 필요한 UserLogin의 스키마를 정의하였다.

// app.ts
// add these below codes
const apiJSDocument = yamljs.load('./api/openapi.yaml');

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

최종적으로 app.ts 파일에 yamljs을 변환하여 json 형식으로 반환된 문서를 /api-docs 경로의 미들웨어로서 추가해주었다.