Python: Building Your First Structured FastAPI Application

FastAPI makes it possible to build HTTP APIs with Python type hints, request validation, and automatically generated OpenAPI documentation. A small application can begin in one file, but separating routes, schemas, and business logic becomes useful as the project grows.

This article builds a minimal API and then outlines a maintainable project structure.

Install FastAPI

Create a virtual environment and install the required packages:

python -m venv .venv

Activate the environment using the command appropriate for the operating system, then install FastAPI and an ASGI server:

pip install fastapi uvicorn

Keeping dependencies inside a virtual environment helps prevent conflicts between projects.

Create a minimal endpoint

Create main.py:

from fastapi import FastAPIapp = FastAPI(title="Tasks API")@app.get("/health")def health_check() -> dict[str, str]:    return {"status": "ok"}

Start the application:

uvicorn main:app --reload

The application is now available locally, typically at http://127.0.0.1:8000. FastAPI also provides interactive API documentation at /docs.

The reload option is useful for development and should not be treated as a production deployment configuration.

Define request and response models

Pydantic models define the structure of request data and provide validation:

from pydantic import BaseModel, Fieldclass TaskCreate(BaseModel):    title: str = Field(min_length=1, max_length=200)    completed: bool = Falseclass TaskResponse(BaseModel):    id: int    title: str    completed: bool

The request model defines what the client may submit. The response model defines the shape of the data returned by the API.

A simple endpoint can accept a request model:

from fastapi import FastAPI, statusapp = FastAPI()@app.post(    "/tasks",    response_model=TaskResponse,    status_code=status.HTTP_201_CREATED,)def create_task(task: TaskCreate) -> TaskResponse:    return TaskResponse(        id=1,        title=task.title,        completed=task.completed,    )

This is a demonstration, not a complete persistence layer: the example always returns ID 1 and does not store tasks. A real implementation would delegate to a repository or database service.

Organize the application

As the API grows, use a structure such as:

app/
    main.py
    api/
        routes/
            tasks.py
    schemas/
        task.py
    services/
        task_service.py
    repositories/
        task_repository.py

The exact structure depends on project size. The goal is to keep HTTP handling, validation models, business rules, and data access understandable and independently testable.

Use dependency injection

FastAPI’s dependency system allows routes to receive shared services without constructing them directly:

from typing import Annotatedfrom fastapi import Dependsdef get_task_service():    return TaskService()@app.get("/tasks")def list_tasks(    service: Annotated[        "TaskService",        Depends(get_task_service)    ]):    return service.list_tasks()

This example assumes a TaskService implementation exists. In a real application, dependency construction should account for database sessions, resource cleanup, and service lifetimes.

Add tests early

FastAPI integrates with Python’s testing ecosystem. Its TestClient can exercise endpoints without starting a separate production server.

Tests should cover valid requests, invalid payloads, expected status codes, and failure conditions. As persistence is introduced, tests should isolate their database dependencies.

Conclusion

FastAPI offers a straightforward starting point for Python APIs, but maintainability comes from clear boundaries rather than framework features alone. Begin with typed request models and focused endpoints, then introduce services, repositories, dependency injection, and tests as the application grows.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *