from fastapi import FastAPI
import nest_asyncio
import uvicorn
# Apply nest_asyncio to allow running FastAPI in Jupyter notebooks
apply() # only needed in Jupyter notebooks
nest_asyncio.
# Create a FastAPI application instance
= FastAPI()
app
# Define a root endpoint
@app.get("/")
def root():
return {"message": f"Hello World"}
# Define a greeting endpoint
@app.get("/greet")
def greet(name: str = "World"):
return {"message": f"Hello {name}"}
# Run the FastAPI application using Uvicorn
="127.0.0.1", port=8000) uvicorn.run(app, host
Creating a RESTful API may sound complicated, but with FastAPI, it’s remarkably straightforward. FastAPI is a modern, high-performance web framework for building APIs with Python, designed to be both easy to use and fast to run.
This blog post, combined with two follow-ups (version 1 (educational) and version 2 (SAP BTP)), is an extended version of a simple FastAPI example I created for a recent hackathon. The goal was to provide a minimal, reusable, beginner-friendly template that’s practical for small projects and prototyping.
In this guide, I’ll walk you through building a simple greeting service using FastAPI, implemented in two common API styles:
- Query-based greeting: A basic version that accepts a name via a URL query parameter using a
GET
request. - JSON-based greeting: A more structured version that accepts a name in a JSON payload using a
POST
request.
The full solution includes:
- A greeting server (this blog post, also available as a notebook on GitHub), which defines the FastAPI application.
- A client notebook demonstrating how to call the API using Python code, with separate notebooks for the
GET
version andPOST
version. - A simple HTML frontend that interacts with the API in the browser, available for both the query-based version and the JSON-based version.
Installation
If you haven’t installed FastAPI yet, you’ll need the following Python packages:
- fastapi (GitHub repo): FastAPI is a modern, high-performance web framework for building APIs with Python, based on standard Python type hints. It handles request parsing, validation, routing, and automatic documentation generation.
- uvicorn (official site | GitHub): Uvicorn is an ASGI (Asynchronous Server Gateway Interface) web server used to run FastAPI applications.
- nest_asyncio (PyPI): This package is required when running asynchronous code like FastAPI inside a Jupyter notebook. It patches the loop to support nested async operations.
You can install all of them using pip:
pip install fastapi uvicorn nest_asyncio
Greeting 1 – Using URL Query Parameters (GET Request)
Let’s start with a simple version of the greeting server that uses query parameters to pass data via a GET
request.
In this version, the user’s name is appended to the URL (e.g., ?name=Christian
), and the server returns a greeting.
The following cell sets up the FastAPI application and starts the server using Uvicorn:
To test the greeting API, you have the following options while the server is running:
- Open http://127.0.0.1:8000/ in your browser for a default “Hello World” message.
- Open http://127.0.0.1:8000/greet?name=Christian in your browser (replace “Christian” with your name) to see your greeting message.
- Use the greeting client notebook to call the greeting API using Python code.
- Access the automatically generated Swagger UI to test and explore the API interactively.
To stop the server, just interrupt the cell execution (e.g., with the ⏹️ stop button).
API Documentation in Detail
FastAPI automatically generates rich API documentation based on the OpenAPI standard. It uses Python type hints and Pydantic models to build a machine-readable specification of your API.
FastAPI provides the following documentation endpoints out of the box:
- OpenAPI schema (JSON): Available at
/openapi.json
, this machine-readable schema defines the structure of your API for tools and clients. - Swagger UI: Accessible at
/docs
, this interactive UI lets you explore and test endpoints directly from your browser. - ReDoc: A more elegant, documentation-focused view is available at
/redoc
.
Both Swagger vs ReDoc are built on the same OpenAPI specification, but serve different purposes. Swagger UI is ideal for developers who want to explore and test the API interactively while ReDoc is better suited for read-only API documentation, it is a more elegant and mobile-friendly view of the API documentation.
Enriching the API Documentation
While FastAPI generates a lot of metadata automatically, you can improve clarity and usability by adding explicit descriptions. In the following example we’ll add the following enhancements to the API documentation:
- API metadata: Set the title, description, and version of the API via
FastAPI(title=..., description=..., version=...)
- Endpoint metadata:
summary
: A short label shown in the UIdescription
: You can add longer explanation. If this parameter is not set, it defaults to the function’s docstringresponse_description
: Text shown next to the response block
- Response models: Defined via Pydantic
BaseModel
classes we can define the structure of the response data. - Query parameter descriptions: Set using
Query(..., description=...)
we can provide a default value and a description for the query parameter.
from fastapi import FastAPI, Query
from pydantic import BaseModel
import nest_asyncio
import uvicorn
apply()
nest_asyncio.
= FastAPI(
app ="Greeting API",
title="A simple API that returns a personalized greeting to the user or the world.",
description="1.0.0"
version
)
class GreetingResponse(BaseModel):
str
message:
@app.get(
"/greet",
="Generates a greeting",
summary#description="Use this parameter if you want to overwrite the function's docstring.",
="A JSON response with the greeting message",
response_description=GreetingResponse,
response_model
)def greet(name: str = Query(default="World", description="The name of the person to greet.")):
"""
Generates a simple greeting.
This endpoint takes a `name` query parameter and returns a JSON object
with a personalized message. If no name is provided, it defaults to "World".
"""
return {"message": f"Hello {name}"}
="127.0.0.1", port=8000) uvicorn.run(app, host
Calling the API
You probably have tested the API using the browser, the greeting client notebook, or the Swagger UI. Let’s make this a little for realistic and call the API using a simple HTML frontend. The HTML-version of the greeting client (download the file and run it locally) contains a simple form that allows you to enter your name and submit it to the API. The response is displayed in the browser.
When you try to call the API in your browser, however, it does not work… The reason is that the HTML file is served from the file (file://{path}/greet-client-get.html
) system, and when you try to call the API from the HTML file using JavaScript (running on http://127.0.0.1:8000/
), you will get a CORS (Cross-Origin Resource Sharing) error. This is because the browser blocks the request to the API from a different origin. This is unlike the API test from the Swagger UI, which runs on the same origin as the API (http://127.0.0.1:8000/
).
To fix this, we need to enable CORS in the FastAPI application. FastAPI provides a middleware to handle CORS requests. When you run the following cell, it will start the FastAPI server with CORS enabled, and you can all the API from the HTML-version of the greeting client.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import nest_asyncio
import uvicorn
apply()
nest_asyncio.
= FastAPI()
app
# Enable CORS so browser fetch() can talk to it
app.add_middleware(
CORSMiddleware,=["*"], # For local testing only
allow_origins=True,
allow_credentials=["*"],
allow_methods=["*"],
allow_headers
)
# Define a greeting endpoint
@app.get("/greet")
def greet(name: str = "World"):
return {"message": f"Hello {name}"}
# Run the FastAPI application using Uvicorn
="127.0.0.1", port=8000) uvicorn.run(app, host
The CORS middleware is required whenever your frontend and backend run on different origins. In production it is therefore needed where APIs and frontends are deployed separately. For local testing, it’s safe to allow all origins with [“*”], but in production, you should restrict access to trusted domains.
Greeting 2 - Using JSON Payload (POST Request)
So far, we have implemented the API using a GET request with a query parameter to pass the name to the server using URL query parameters. This is a common pattern for simple APIs, but it has some limitations: The URL length is limited, and it is not suitable for complex data structures. Therefore, we will implement a second version of the greeting server, which uses a JSON payload to pass the name to the server using a POST request:
{
"name": "Christian"
}
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
import nest_asyncio
import uvicorn
# Allow uvicorn to run inside the notebook
apply()
nest_asyncio.
# Define the FastAPI app
= FastAPI()
app
# Enable CORS so browser fetch() can talk to it
app.add_middleware(
CORSMiddleware,=["*"], # For local testing only
allow_origins=True,
allow_credentials=["*"],
allow_methods=["*"],
allow_headers
)
# Define a Pydantic model for the expected JSON input
class NameRequest(BaseModel):
str
name:
# POST endpoint that expects JSON input
@app.post("/greet")
def greet_name(request: NameRequest):
return {"message": f"Hello {request.name}"}
# Start the FastAPI server (in the same cell!)
="127.0.0.1", port=8000) uvicorn.run(app, host
Testing this API in the browser is not possible, but we need a form to submit the data. You can choose one of the following options:
- Run the Greeting Client Notebook
- Use the HTML-version of the greeting client to submit the data via a form in your browser (download the file and open it in your browser).
- Use the Swagger UI to test the API interactively.
Regarding documentation, we can use the same approaches as for the first version of the greeting server.
Putting It All Together
To wrap up this blog post, the following cell contains everything we’ve discussed in one place. It implements the Greeting API using both GET
and POST
methods. Note that both endpoints are on the same route (/greet
). FastAPI allows us to define multiple HTTP methods for the same endpoint by using different decorators (@app.get
and @app.post
).
This version also includes improved metadata and documentation. You can explore the API through the:
- Swagger UI – interactive and developer-friendly
- ReDoc documentation – elegant and mobile-friendly
- OpenAPI schema – machine-readable JSON
Finally, I included a more realistic CORS configuration that allows the API to be accessed only from localhost
(typically 127.0.0.1
, as used during local testing) or from the test UI deployed on GitHub Pages. The only caveat is that testing directly from the file system (file://
) no longer works, since file://
cannot be specified as a valid CORS origin.
Nonetheless, once you run the following cell, you can test both versions of the API from with these resources:
- The client notebooks for
GET
andPOST
- The HTML frontends for query-based and JSON-based versions
from fastapi import FastAPI, Query
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
import nest_asyncio
import uvicorn
# Allow FastAPI to run inside a Jupyter notebook
apply()
nest_asyncio.
# Define the FastAPI app
= FastAPI(
app ="Greeting API",
title="A simple API that returns a personalized greeting, either via URL query parameters or JSON payload.",
description="1.0.0"
version
)
# Enable CORS (for local testing and GitHub Pages deployment)
app.add_middleware(
CORSMiddleware,=[
allow_origins"http://localhost:8000", # Local testing
"http://127.0.0.1:8000", # Also valid local reference
"https://chrwittm.github.io", # GitHub Pages deployment
],=True,
allow_credentials=["*"],
allow_methods=["*"],
allow_headers
)
# Define Pydantic model for POST request
class NameRequest(BaseModel):
str
name:
# Pydantic model for response (shared across GET and POST)
class GreetingResponse(BaseModel):
str
message:
# GET endpoint using query parameter
@app.get("/greet", response_model=GreetingResponse, summary="Greet via query parameter")
def greet_get(name: str = Query(default="World", description="The name of the person to greet.")):
"""
Returns a greeting based on the query parameter 'name'.
"""
return {"message": f"Hello {name}"}
# POST endpoint using JSON body
@app.post("/greet", response_model=GreetingResponse, summary="Greet via JSON payload")
def greet_post(request: NameRequest):
"""
Returns a greeting based on the JSON body with a 'name' field.
"""
return {"message": f"Hello {request.name}"}
# Run the app locally in the notebook
="127.0.0.1", port=8000) uvicorn.run(app, host
Conclusion
In this post, we’ve explored how to build a simple yet well-structured API using FastAPI. We started with a lightweight GET endpoint using query parameters and then moved on to a more robust POST endpoint that accepts JSON payloads. Along the way, we learned how FastAPI automatically generates interactive documentation using the OpenAPI standard. Additionally, we explored how we can enrich the documentation through type annotations, docstrings, and metadata. Even though our greeting service is simple, the patterns we’ve used scale well to real-world applications.
In follow-up posts, available in two versions (using Render for educational purposes or using SAP BTP for enterprise scale), we’ll look at how to deploy this service locally or in the cloud.