Autogenerating Clients with FastAPI and Github Actions

Autogenerating Clients with FastAPI and Github Actions

If you’ve ever hand-written a javascript/typescript client for an API, you probably know the pain of writing a subtle bug. Maybe you missed a query parameter or did some copy/pasting that you forgot to modify. Or maybe the API just changed later on and the client was never updated. While they may be simple mistakes, they are a pain to debug and coordination problems like these will only get worse as your team grows.

Coordination via OpenAPI + FastAPI

One solution to this challenge is OpenAPI — a spec that allows you to write JSON or YML, which describes your API. You can then autogenerate clients and servers in different languages and guarantee that the client and server are in sync.

However, a problem that I’ve always had with this approach is that I personally don’t like editing the OpenAPI spec for every change I want to make. While there are tools that make the process easier, as a backend developer, I prefer to update my routes directly on the backend.

This is where FastAPI comes in. FastAPI is a python web framework with a lot of thoughtful features. One of my favorite features is that it will generate an OpenAPI spec from the code you write. For example, let’s use this app:

from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/hello")
async def hello(name: Union[str, None] = None):
    if name is None:
        return {"message": "Hello World"}
    else:
        return {"message": f"Hello {name}"}

This is a simple app that has one route, an optional query parameter, and a JSON response.

Next, run the server:

$ uvicorn main:app

Then navigate to http://localhost:8000/openapi.json, where you’ll see the OpenAPI spec that matches the API.

API First Design

This changes the workflow from:

Update OpenAPI schema ⇒ Generate server routes ⇒ Generate clients

to:

Update your server ⇒ Generate the OpenAPI schema ⇒ Generate the clients

While there are merits to both, the FastAPI version feels way more natural to me as it gets my backend into a good state and then has everything else derive from that.

Automatically updating the clients with Github Actions

If there’s only one client of your API, it’s not so bad to re-generate it every time you need make a change. But as the number of clients increases or your team size increases, you’ll want to automate this process in order to avoid drowning in manual updates.

Adding in Github Actions will enable you to generate new clients on every commit. Let’s first look at the action and then break it down:

name: Generate clients
on: push
jobs:
  generate-clients:
    runs-on: ubuntu-latest
    name: Example
    steps:
    - uses: actions/checkout@master
    - name: Set up Python 3.9
      uses: actions/setup-python@v1
      with:
        python-version: 3.9

    - name: Install 
      run: >-
        python -m
        pip install -r requirements.txt
        --user

    - name: Run server in background
      run: uvicorn main:app &
  
    - name: Generate OpenAPI document 
      run: curl localhost:8000/openapi.json > openapi.json

    - name: Cleanup old client
      run: rm -rf typescript-fetch-client/

    - name: Generate new client
      uses: openapi-generators/openapitools-generator-action@v1
      with:
        generator: typescript-fetch

    - name: Commit new client
      run: |
        git config --global user.name 'Your Name'
        git config --global user.email 'yourname@yourcompany.com'
        git add typescript-fetch-client/
        git commit -am "Update typescript client"
        git push

This action runs on every push and will do the following:

  • Setup python
  • Install your projects dependencies from the requirements.txt file
  • Run our server in the background
  • Download our openapi.json file (note: the server starts up very quickly for this example, if yours is slower, you may need to add a sleep or use something like wait-on)
  • Use the OpenAPITools Generator action to generate a client. We generated a typescript client based on fetch, but you can create as many clients as you want here.
  • Finally, we committed the new client back to our repo. We could have also pushed it to a separate repo, or published it to NPM so others can use it.

And that’s all! Now every time anyone makes a change to the API, new clients will automatically be generated to match the API thanks to FastAPI’s OpenAPI support. If you want to learn more about FastAPI, check out our related blogs on React + FastAPI Authentication, Dependency Injection with FastAPI's Depends, and RBAC Authorization with FastAPI and PropelAuth.