DEV Community

Yeongseon Choe
Yeongseon Choe

Posted on • Originally published at Medium

FastAPI-Style Swagger UI for Azure Functions in Python (OpenAPI + Pydantic)

I've been building stuff with Azure Functions in Python recently, and I kept running into the same annoyance:

"Where's my Swagger UI?"

FastAPI gives you this beautiful auto-generated Swagger doc with just a few decorators and some pydantic models.

But Azure Functions? Nothing built-in. No docs. No OpenAPI JSON. Just function.json files and… sadness.

So I decided to do something about it.

💡 What I built

I started hacking on a decorator that could hook into the Azure Function's internals — and a few weekends later, I had this:

  • A @openapi decorator that adds summary, description, tags, models, etc.
  • Automatic method and route detection from function.json
  • pydantic support for request and response schemas
  • Swagger UI served at /api/docs
  • JSON docs at /api/openapi.json and /api/openapi.yaml

All without needing to run a separate web server.

✍️ How it works (with an example)

Here's what it looks like in action:

# hello_openapi/function_app.py

import json
import azure.functions as func
from azure_functions_openapi.decorator import openapi
from azure_functions_openapi.openapi import get_openapi_json, get_openapi_yaml
from azure_functions_openapi.swagger_ui import render_swagger_ui

app = func.FunctionApp()

@openapi(
    summary="Greet user",
    route="/api/http_trigger",
    request_model={"name": "string"},
    response_model={"message": "string"},
    tags=["Example"]
)
@app.function_name(name="http_trigger")
@app.route(route="/api/http_trigger", auth_level=func.AuthLevel.ANONYMOUS, methods=["POST"])
def main(req: func.HttpRequest) -> func.HttpResponse:
    try:
        data = req.get_json()
        name = data.get("name", "world")
        return func.HttpResponse(
            json.dumps({"message": f"Hello, {name}!"}),
            mimetype="application/json"
        )
    except Exception as e:
        return func.HttpResponse(f"Error: {str(e)}", status_code=400)

@app.function_name(name="openapi_json")
@app.route(route="/api/openapi.json", auth_level=func.AuthLevel.ANONYMOUS, methods=["GET"])
def openapi_json(req: func.HttpRequest) -> func.HttpResponse:
    return get_openapi_json()

@app.function_name(name="openapi_yaml")
@app.route(route="/api/openapi.yaml", auth_level=func.AuthLevel.ANONYMOUS, methods=["GET"])
def openapi_yaml(req: func.HttpRequest) -> func.HttpResponse:
    return get_openapi_yaml()

@app.function_name(name="swagger_ui")
@app.route(route="/api/docs", auth_level=func.AuthLevel.ANONYMOUS, methods=["GET"])
def swagger_ui(req: func.HttpRequest) -> func.HttpResponse:
    return render_swagger_ui()
Enter fullscreen mode Exit fullscreen mode

Once you register the Swagger UI route manually, deploying your function lets you access the full documentation at /api/docs.

🚀 Quick Start

pip install azure-functions-openapi
Enter fullscreen mode Exit fullscreen mode

Then:

  • Decorate your functions with @openapi(...)
  • Define your request/response models with pydantic or dict
  • Register /api/docs, /api/openapi.json, and /api/openapi.yaml routes
  • Deploy and visit /api/docs 🎉

🔗 GitHub

All the code is open-source here:

👉 https://github.com/yeongseon/azure-functions-openapi

If you try it out, I'd love to hear how it works for you.

PRs, issues, suggestions — all welcome 🙌


Originally published on Medium.

Top comments (0)