How to Create OpenAPI Document with Swagger
Published: 2021-11-23 13:18:53

## Purpose of This Post
We can create an OpenAPI document using [Swagger](https://swagger.io). In this post I would like to try to give a short explanation of how to create an API document using Swagger.
If you want to know the final outcome first, please take a look at [Summary](#summary) first.

## About OpenAPI
> **OpenAPI Specification** (formerly Swagger Specification) is an API description format for REST APIs.

(Sited from https://swagger.io/docs/specification/about/.)

## Preparation
First, open Swagger Editor (https://editor.swagger.io). You will see the page like this:
![Pet Store Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k2fu5tpakuc5jhltdsea.png)

Second, clear the YAML content.
![No content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4pdgehb35eeia7n42fc.png)

## Let's Start with a Simple Example
Here is a simple example that just contains info and servers of this API:

```yaml
openapi: 3.0.3
info:
  version: 1.0.0
  title: Simple Todo API
  description: This is a simple API for todo notes
  
servers:
  - url: https://example.com/v1

paths: {}
```

Let me explain it briefly. The `openapi` specifies the version of the OpenAPI specification. This `openapi` field is required for each OpenAPI document.
The `info` object contains two required fields, `version` and `title`. The `version` specifies the version of the OpenAPI document (not OpenAPI Specification version nor the API implementation version) and the `title` specifies the title of the API. The `info` object also contains `description` field, which is optional. It is used for a short description of the API.
The `servers` array specifies server URLs for API calls. In the above example, the server URL is `https://example.com/v1`.

At this point, our OpenAPI document is like:
![Simple Example Without Any Paths](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ty7nz1ioie4qwioqihg0.png)

## Let's Add Resource Paths
You may have already noticed that `paths` object was left as empty in previous section. Let's define some endpoints in this section.

Suppose that a client can:
- get all notes by sending a GET request to `/notes` endpoint.
- get a note by note ID by sending a GET request to `/notes/{noteId}` endpoint where `{noteId}` is a note ID.
- create a note by sending a POST request to `/notes` endpoint.

Also suppose that note object is represented as the following JSON object:
```jsonc
{
  "id": 1, // integer
  "title": "sample title", // string
  "content": "sample content" // string
}
```

### Tags
Before defining endpoints, we will define a tag in `tags` like:
```yaml
tags:
  - name: notes
    description: Everything about your notes
```

The `tags` object is not required, but by defining tags we can group our endpoints.

At this point, our OpenAPI document is like:
![Tags](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5mgmiggef0fv1tic9389.png)

### Path Item
First let's define the `/notes` and the GET method for this endpoint. Replace the `paths` object with:
```yaml
paths:
  /notes: # A relative path to an individual endpoint.
    get: # GET method on this path.
      tags: 
        - notes
      summary: Find all notes
      description: Returns all notes
      responses:
        '200': # HTTP Status Code
          description: Successful operation
          content: {} # Described later.
```
![GET /notes Without content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p0hdpnbvw53jgvclaux6.png)
![Detail of GET /notes Without content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ie2867ie2lskefhwoiuw.png)

We haven't yet defined the response body of a GET request to `/notes` endpoint, so we will define it. Modify `paths` to:
```yaml
paths:
  /notes:
    get:
      tags: 
        - notes
      summary: Find all notes
      description: Returns all notes
      responses:
        '200':
          description: Successful operation
          content:
            application/json: # media type
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                      format: int64
                    title:
                      type: string
                      example: Sample title
                    content:
                      type: string
                      example: Sample content
```
Look at the `schema`. Its structure corresponds to that of a list of note objects defined before:
```jsonc
[
  {
    "id": 1, // integer
    "title": "sample title", // string
    "content": "sample content" // string
  }
]
```

That's it for our first path item. This definition indicates that if a client calls `GET https://example.com/v1/notes`, he/she will receive 200 HTTP status code with a list of notes like:
```json
[
  {
    "id": 1,
    "title": "title 1",
    "content": "content 1"
  },
  {
    "id": 2,
    "title": "title 2",
    "content": "content 2"
  }
]
``` 

At this point, our OpenAPI document is like:
![Detail of GET /notes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t91jgv6396dua0oji6bd.png)

### Parameters
Next let's define the `/notes/{noteId}` and the GET method for this endpoint. Add the following to the `paths` object:
```yaml
  /notes/{noteId}:
    get:
      tags:
        - notes
      summary: Find note by ID
      description: Returns a single note
      parameters: # You can define parameters here.
        - name: noteId
          in: path
          required: true
          description: ID of note to return
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                    format: int64
                  title:
                    type: string
                    example: Sample title
                  content:
                    type: string
                    example: Sample content
        '404':
          description: Note not found
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    format: int32
                  message:
                    type: string
```
It's basically the same as the definition of GET method for `/notes`. One of a difference is the existence of 404 HTTP status code response. We can use any HTTP status codes as property names.
Another important difference is the `parameters` field. We can define parameters (such as path parameter, query parameter) in this `parameters` field. Here is the part of the `parameters`:
```yaml
      parameters:
        - name: noteId
          in: path # This is a path parameter.
          required: true # This parameter is required.
          description: ID of note to return
          schema:
            type: integer # This is an integer parameter.
            format: int64
```

At this point, our OpenAPI document is like:
![GET /notes/{noteId}](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/872co0qsr8tonkp1er36.png)

### Components
As you may have noticed, we defined the note object twice. We can avoid defining the same object many times using the `components` object. Let's define `Note` and `ApiResponse` and reuse them. Add the following `components` object:

```yaml
components:
  schemas:
    Note:
      type: object
      properties:
        id:
          type: integer
          format: int64
        title:
          type: string
          example: Sample title
        content:
          type: string
          example: Sample content
    ApiResponse:
      type: object
      properties:
        status:
          type: integer
          format: int32
        message:
          type: string
```

Then modify the `paths` object to:
```yaml
paths:
  /notes:
    get:
      tags: 
        - notes
      summary: Find all notes
      description: Returns all notes
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Note' # New
  
  /notes/{noteId}:
    get:
      tags:
        - notes
      summary: Find note by ID
      description: Returns a single note
      parameters:
        - name: noteId
          in: path
          required: true
          description: ID of note to return
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note' # New
        '404':
          description: Note not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse' # New
```

At this point, our OpenAPI document is like:
![Components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sq7fun97q10l95dckgbe.png)
![Detail of Components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4h6ixhb7hyhl49lx0f6.png)

### Request Body
Finally let's add the POST method for `/notes` endpoint. Add the following object to `/notes`:
```yaml
    post: # POST method
      tags:
        - notes
      summary: Add a new note
      description: Tries to craete a new note and returns a message
      requestBody: # We can define the request body here.
        required: true
        description: Note object that needs to be added
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Note'
      responses:
        '200':
          description: Successfully added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'
        '500':
          description: Unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'
     
```
Since this is a definition for POST method, `post` is used.
Also, there is a new field, `requestBody`. We can define the request body using the `requestBody` field. Here is the part of `requestBody`:
```yaml
      requestBody: # We can define the request body here.
        required: true
        description: Note object that needs to be added
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Note'
```
Now our OpenAPI document is like:
![POST /notes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bia0qwn3y0hyvr74dh8h.png)
![Detail of POST /notes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4v3c4otj22qchvjxrf8q.png)

## Summary
<a name="summary"></a>That's it. I made a short explanation of how to create an OpenAPI document using Swagger.
Following is the final outcome:
```yaml
openapi: 3.0.3
info:
  version: 1.0.0
  title: Simple Todo API
  description: This is a simple API for todo notes

servers:
  - url: https://example.com/v1

tags:
  - name: notes
    description: Everything about your notes
    
paths:
  /notes:
    get:
      tags: 
        - notes
      summary: Find all notes
      description: Returns all notes
      responses:
        '200':
          description: Successful operation
          content:
            application/json: # media type
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Note'
    post: # POST method
      tags:
        - notes
      summary: Add a new note
      description: Tries to craete a new note and returns a message
      requestBody: # We can define the request body here.
        required: true
        description: Note object that needs to be added
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Note'
      responses:
        '200':
          description: Successfully added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'
        '500':
          description: Unexpected error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'

  /notes/{noteId}:
    get:
      tags:
        - notes
      summary: Find note by ID
      description: Returns a single note
      parameters:
        - name: noteId
          in: path
          required: true
          description: ID of note to return
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Note'
        '404':
          description: Note not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'

components:
  schemas:
    Note:
      type: object
      properties:
        id:
          type: integer
          format: int64
        title:
          type: string
          example: Sample title
        content:
          type: string
          example: Sample content
    ApiResponse:
      type: object
      properties:
        status:
          type: integer
          format: int32
        message:
          type: string
```

## Reference
- https://swagger.io/docs/specification/about/
- https://swagger.io/specification/
- https://support.smartbear.com/swaggerhub/docs/tutorials/openapi-3-tutorial.html