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