Documentation of APIs can be tedious and needs to cater to different use cases such as exploring, testing and coding. This creates the need for both machine-readable as well as human-readable formats, which in the latter case often needs an interactive user interface as well.
Several tools and standards have emerged over the years to integrate these different approaches. In this blog post, we are going to explore the possibilities of a common specification format for RESTful APIs, OpenAPI.
The OpenAPI initiative emerged as an Open Source project from the Swagger project in 2015 and is now at its latest version 3.1.
At the heart of this toolkit is a YAML file that specifies the API.
The OpenAPI specification file
The basic structure of an OpenAPI specification consist of the following:
openapi: 3.0.0
info:
  description: This is an example API documentation
  title: API Docs Sample
  version: '1.0.0'
servers:
  - url: 'https://api.example.com'
    description: The Sample API
paths: # see examples below
components:
  securitySchemes:
    Basic:
      description: basic http authentication
      type: http
      scheme: basic
  schemas:
    Date:
      type: string
      format: date-time
  requestBodies: # see examples below
  responses: # see examples below
tags:
  - description: An example tag
    name: SampleIn this example you see some basic info, the endpoint base URL (in this case https://api.example.com) and a Basic HTTP authentication scheme.
These are followed by a set of endpoint paths, definitions for schemas, requestBodies and responses, and a list of tags that are used to group certain endpoints.
Schemas
Let's start by adding some schemas for our sample resource.
OpenAPI allows you to combine several schemas into new schemas, so we will be able to apply some kind of inheritance:
  schemas:
    Date:
      type: string
      format: date-time
    ResourceBase:
      properties:
        value:
          type: number
          example: 42
    ResourceCreate:
      allOf:
        - properties:
            id:
              type: string
              example: 00a9b9bf-c099-45aa-9278-64d6a3f22476
        - $ref: '#/components/schemas/ResourceBase'
    ResourceFull:
      allOf:
        - properties:
            date:
              $ref: '#/components/schemas/Date'
        - $ref: '#/components/schemas/ResourceCreate'As you can see in this example, the ResourceCreate schema inherits from ResourceBase and adds a property id. The ResourceFull schema adds an extra property date.
Now, these schemas can be used to define request bodies and responses.
Requests
First, the request bodies. When creating a resource, we will use the ResourceCreate schema to define the resource properties.
When updating the resource, we will use the ResourceBase schema:
  requestBodies:
    RequestCreate:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ResourceCreate'
      required: true
    RequestUpdate:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ResourceBase'
      required: trueResponses
Calls to resource endpoints will generate certain responses, which should also be defined:
  responses:
    ResponseResource:
      content:
        application/json; charset=utf-8:
          schema:
            $ref: '#/components/schemas/ResourceFull'
      description: OK
    ResponseNotFound:
      content:
        application/json; charset=utf-8:
          schema:
            properties:
              message:
                type: string
            required:
              - message
            type: object
          example:
            message: resource not found
      description: Not Found
    ResponseUpdated:
      content:
        application/json; charset=utf-8:
          schema:
            properties:
              message:
                type: string
            required:
              - message
            type: object
          example:
            message: resource updated
      description: OK
    ResponseDeleted:
      content:
        application/json; charset=utf-8:
          schema:
            properties:
              message:
                type: string
            required:
              - message
            type: object
          example:
            message: resource deleted
      description: OK
    ResponseCreated:
      content:
        application/json; charset=utf-8:
          schema:
            properties:
              id:
                type: string
                example: 00a9b9bf-c099-45aa-9278-64d6a3f22476
            type: object
      description: CreatedPaths
Now, since we have specified all our data structures, we can map all our endpoints for the resource.
Create
paths:
  /resource:
    post:
      security:
        - Basic: []
      responses:
        '201':
          $ref: '#/components/responses/ResponseCreated'
      tags:
        - Sample
      description: Create a resource
      operationId: postResource
      requestBody:
        $ref: '#/components/requestBodies/RequestCreate'
      summary: Create a resourceRetrieve
paths:
  '/resource/{id}':
    get:
      security:
        - Basic: []
      parameters:
        - description: The unique identifier for your resource.
          example: '00a9b9bf-c099-45aa-9278-64d6a3f22476'
          in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          $ref: '#/components/responses/ResponseResource'
        '404':
          $ref: '#/components/responses/ResponseNotFound'
      tags:
        - Sample
      description: Retrieve an existing resource
      operationId: getResource
      summary: Retrieve an existing resourceUpdate
paths:
  '/resource/{id}':
    get: # see above
    put:
      security:
        - Basic: []
      parameters:
        - description: The unique identifier for your resource.
          example: '00a9b9bf-c099-45aa-9278-64d6a3f22476'
          in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          $ref: '#/components/responses/ResponseUpdated'
        '404':
          $ref: '#/components/responses/ResponseNotFound'
      tags:
        - Sample
      description: Update an existing resource
      operationId: putDevice
      requestBody:
        $ref: '#/components/requestBodies/RequestUpdate'
      summary: Update an existing resourceDelete
paths:
  '/resource/{id}':
    get: # see above
    put: # see above
    delete:
      security:
        - Basic: []
      parameters:
        - description: The unique identifier for your resource
          example: '00a9b9bf-c099-45aa-9278-64d6a3f22476'
          in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          $ref: '#/components/responses/ResponseDeleted'
        '404':
          $ref: '#/components/responses/ResponseNotFound'
      tags:
        - Sample
      description: Delete a resource
      operationId: deleteResource
      summary: Delete an existing resourceAt this point, you can use this OpenAPI specification file to generate stub code for API clients, schemas for Databases, server code for testing.
An example overview of tools and generators can be found at the OpenAPI Generator Website.
Generate interactive documentation
In this blog post, we are going to use ReDoc (see their example page for more info) to render our documentation. Let's set up a simple HTML document to embed the ReDoc viewer:
<!DOCTYPE html>
<html>
<head>
    <title>API Docs Sample</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="main-content">
    <div id="redoc-container"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"></script>
<script type="text/javascript">
  (function() {
    Redoc.init(
      "specs/api-docs-sample.yaml",
      {},
      document.getElementById('redoc-container'));
  })();
</script>
</body>
</html>Opening this in a browser will indeed show us an interactive documentation viewer:
      
  
    
Example payloads
      
  
    
Of course this can also be customised with your own look and feel. For an example of a customised documentation viewer, take a look at the Notificare API Docs.
Conclusion
Using a structured specification format like OpenAPI, you can easily generate code and documentation for your REST APIs by using the generator tools out there.
If you have any corrections, suggestions or you simply want to know more about our API documentation, as always, you can contact us via our Support Channel


