Interactive docs for your API

Joris Verbogt
Joris Verbogt
Mar 19 2021
Posted in Engineering & Technology

Leverage the power of OpenAPI and ReDoc

Interactive docs for your API

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: Sample

In 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: true

Responses

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: Created

Paths

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 resource

Retrieve

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 resource

Update

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 resource

Delete

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 resource

At 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:

redoc viewer

Example payloads

redoc payload

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

Keep up-to-date with the latest news