Implementing a Greeting API with FastAPI

fastapi
jupyter
Author

Christian Wittmann

Published

July 8, 2025

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:

The full solution includes:

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:

from fastapi import FastAPI
import nest_asyncio
import uvicorn

# Apply nest_asyncio to allow running FastAPI in Jupyter notebooks
nest_asyncio.apply() # only needed in Jupyter notebooks

# Create a FastAPI application instance
app = FastAPI()

# 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
uvicorn.run(app, host="127.0.0.1", port=8000)

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 UI
    • description: You can add longer explanation. If this parameter is not set, it defaults to the function’s docstring
    • response_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

nest_asyncio.apply()

app = FastAPI(
    title="Greeting API",
    description="A simple API that returns a personalized greeting to the user or the world.",
    version="1.0.0"
)

class GreetingResponse(BaseModel):
    message: str

@app.get(
    "/greet",
    summary="Generates a greeting",
    #description="Use this parameter if you want to overwrite the function's docstring.",
    response_description="A JSON response with the greeting message",
    response_model=GreetingResponse,
)
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}"}

uvicorn.run(app, host="127.0.0.1", port=8000)

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

nest_asyncio.apply()

app = FastAPI()

# Enable CORS so browser fetch() can talk to it
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # For local testing only
    allow_credentials=True,
    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
uvicorn.run(app, host="127.0.0.1", port=8000)

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
nest_asyncio.apply()

# Define the FastAPI app
app = FastAPI()

# Enable CORS so browser fetch() can talk to it
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # For local testing only
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Define a Pydantic model for the expected JSON input
class NameRequest(BaseModel):
    name: str

# 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!)
uvicorn.run(app, host="127.0.0.1", port=8000)

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:

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:

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:

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
nest_asyncio.apply()

# Define the FastAPI app
app = FastAPI(
    title="Greeting API",
    description="A simple API that returns a personalized greeting, either via URL query parameters or JSON payload.",
    version="1.0.0"
)

# 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
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Define Pydantic model for POST request
class NameRequest(BaseModel):
    name: str

# Pydantic model for response (shared across GET and POST)
class GreetingResponse(BaseModel):
    message: str

# 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
uvicorn.run(app, host="127.0.0.1", port=8000)

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.