14.11.2024

API Documentation with FastAPI: A Look at the Most Important Functions.

FastAPI Documentation with Programming Examples

There is one thing that always drives us when we want to build a good API - the documentation. Especially for publicly consumable APIs, one never knows who uses them and under what conditions the respective developer's assumptions are. To promote a particularly good DX (= Developer Experience), we provide our OpenAPI documentations with code examples in many languages.

Blueshoe and FastAPI: Documentation with Programming Examples

Documentation via OpenAPI

First the basics - FastAPI is one of the frameworks we use to write APIs. It is based on Python and works with very good performance. FastAPI is strongly typed, which means that input and output types are largely known in the program (within the framework possible in Python).

Through the known types, an API schema can be generated. This schema contains all necessary information about the available endpoints. OpenAPI is used as the standard format here. Example of an OpenAPI Endpoint:

openapi: 3.1.0
info:
  title: Redocly Museum API
  description: An imaginary, but delightful Museum API for interacting with museum services and information. Built with love by Redocly.
  version: 1.0.0
  contact:
    email: team@redocly.com
    url: 'https://redocly.com/docs/cli/'
  x-logo:
    url: 'https://redocly.github.io/redoc/museum-logo.png'
    altText: Museum logo
  license:
    name: MIT
    url: 'https://opensource.org/licenses/mit/'
servers:
  - url: 'https://api.fake-museum-example.com/v1'
paths:
  /museum-hours:
    get:
      summary: Get museum hours
      description: Get upcoming museum operating hours
      operationId: getMuseumHours
      x-badges:
        - name: 'Beta'
          position: before
          color: purple
      tags:
        - Operations
      parameters:
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/PaginationPage'
        - $ref: '#/components/parameters/PaginationLimit'
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetMuseumHoursResponse'
              examples:
                default:
                  summary: Museum opening hours
                  value:
                    - date: '2023-09-11'
                      timeOpen: '09:00'
                      timeClose: '18:00'
                    - date: '2023-09-12'
                      timeOpen: '09:00'
                      timeClose: '18:00'
                    - date: '2023-09-13'
                      timeOpen: '09:00'
                      timeClose: '18:00'
                    - date: '2023-09-17'
                      timeOpen: '09:00'
                      timeClose: '18:00'
                  closed:
                    summary: The museum is closed
                    value: []

      '400':
        description: Bad request
      '404':
        description: Not found

The generated specification contains examples, possible answers, and a description of the endpoint parameters.

Programs like Swagger or Redoc use this schema and generate a human-readable view in the Browser:

Redoc Example

Here information such as authentication, pagination, and filtering for the endpoint are presented.

Examples of API Documentation

A major added value of the OpenAPI standard is that structured data can also generate examples. This looks in Redoc, for example, as follows:

Redoc API Call

The examples can be explicitly specified during development or automatically generated with simple data (such as 0 for integers, "string" for chains). These example data for an HTTP Post can then be created automatically.

Request to show only the payloads from our FastAPI. To support the developers who use our API, we want to embed concrete programming examples. This can be easily implemented with Redoc via the x-codeSamples tag. But where do we get the examples without learning every programming language ourselves?

Generation of Code Examples - Packages

On Github, we have found 2 packages that seem suitable for generating API code examples: postman-code-generators and httpsnippet. postman-code-generators originates from Postman, a company focused on developer tooling for APIs. It has its own data structure to model HTTP requests.

httpsnippet comes from Kong, a company that primarily develops an API Gateway. It works with the HTTP Archives standard. Both packages are well-maintained and for pragmatic reasons, we choose postman-code-generators. The data we have in our OpenAPI Schema can be quite easily transferred to Postman's structures.

Converting OpenAPI into Postman Structures

First, the structure of the Postman SDK. To work with postman-code-generators, the SDK postman-collection will additionally be used.

const codegen = require('postman-code-generators')
const sdk = require('postman-collection')


const baseUrl = "https://our-api.io"
const openapiJSON = getOpenApiJSON();
const supportedCodegens = codegen.getLanguageList()

postman-code-generators comes with a list of languages, which we can simply iterate through to generate code examples.

for (const codegen of supportedCodegens) {
   language = codegen.key;
   languageLabel = codegen.label;
   for (const variation of codegen.variants) {
       variant = variation.key
       generateSamples()
   }
}

Each language has different variants, which can basically represent different possibilities of making HTTP requests - e.g. http and requests for Python. Our generateSamples function now simply generates a code example for each endpoint (here only POST endpoints) in the current language:

function generateSamples() {
   // generate samples for open api endpoint paths
   const paths = openapiJSON["paths"]
   for (const [path, operation] of Object.entries(paths)) {
       currentPath = path;
       const data = generateExamplePayload(operation["post"], openapiJSON)
       convertEndpoint(path, "POST", data, addEntry)
   }
}

Within the payload generation (generateExamplePayload), the Postman Request Object will then be created:

function buildRequest(url, method, data) {
   // build postman request
   return new sdk.Request({
       url: `${baseUrl}${url}`,
       method,
       body: buildBody(data),
       header: {
           ...header,
       }
   })
}

Since we are working with schemas in our openapi.json file, we use another package that helps us generate example payloads for these schemas:

var OpenAPISampler = require('openapi-sampler');
const schema = spec["components"]["schemas"][name]
   // Generate sample data
   return OpenAPISampler.sample(
       schema,
       {},
       spec
   )

Finally, we wrap up our loop and the code generation.

function convertEndpoint(path, method, data, cb) {
   const request = buildRequest(path, method, data)
   codegen.convert(language, variant, request, sampleFormattingOptions, cb);
}

As a last step, we add the generated examples to our openapi.json file:

function addEntry(error, snippet) {
   if (error) {
       console.log(error, language, variant)
       return
   }
   const xCodeSample = {
       lang: languageLabel,
       label: `${languageLabel} (${variant})`,
       source: snippet
   }
   if (openapiJSON["paths"][currentPath]["post"]["x-codeSamples"]) {
       openapiJSON["paths"][currentPath]["post"]["x-codeSamples"].push(xCodeSample)

   } else {
       openapiJSON["paths"][currentPath]["post"]["x-codeSamples"] = [xCodeSample]
   }
}

And now our openapi.json file contains code examples from all possible languages.

Redoc all Clients

Conclusion

Developing an API and writing documentation is not just for self-purpose. It's about considering the users / developers who will consume our REST API as an example. The API consumers can retrieve them where they stand, thus making the use of the API further facilitated, which should especially be the goal for public interfaces.

BLUESHOE GmbH
© 2024 BLUESHOE GmbH