You have a web app that needs to talk to an ERP, an e-commerce site, or an internal dashboard. You write an API in Python. You use Flask because you know it. Then the client says: “Data arrives corrupted, the system crashes if a null field passes, and documentation? There is none.”
We at Meteora Web have seen this script dozens of times. That’s why, when building APIs for Italian SMEs — that must work immediately, without unlimited server budgets — we choose FastAPI. Not because it’s trendy. Because it’s fast, secure by default, and gives you free documentation. In this guide, we show you how to start from zero and build a REST API that holds its own against any enterprise stack, with Python code and minimal investment.
Why FastAPI beats Flask and Django for modern APIs?
Let’s do the math. On a real project for a clothing retailer, we had a Flask API handling 200 requests per second. After migrating to FastAPI, on the same machine, we reached 1800 requests per second. The technical reason: FastAPI uses ASGI (async) instead of WSGI (sync). But for you it means one thing: fewer servers, more customers served, infrastructure cost cut by 70%.
Flask is fine for prototypes, Django is a cannon for complex sites. FastAPI is the right screwdriver for APIs that must be performant, automatically validated, and documented without writing a single line of Swagger by hand.
Sponsored Protocol
Pydantic validation: why dirty data costs you money
When a POST request arrives with a 'price' field that’s a string instead of a float, what happens? If you don’t check, the app crashes or, worse, saves inconsistent data. Pydantic is the library where you declare a schema, and it automatically validates, transforms, and serializes. We’ve used it since our accounting days: the difference between data checked upstream and data cleaned downstream is a balanced ledger or a VAT hole.
How to structure a FastAPI project for an SME?
You don’t need a 12-microservice architecture. For an SME, a clear structure with models, routers, dependencies, and configuration is sufficient. Here’s our battle-tested template:
# directory structure:
# app/
# ├── __init__.py
# ├── main.py # FastAPI instance, router inclusion
# ├── config.py # settings from environment variables
# ├── models.py # Pydantic schemas
# ├── routers/
# │ ├── __init__.py
# │ ├── clients.py # CRUD endpoints for clients
# │ ├── orders.py # order endpoints
# ├── dependencies.py # dependencies (e.g., auth)
# ├── database.py # DB connection (if needed)
# └── schemas.py # Pydantic for request/response
Start with main.py:
Sponsored Protocol
from fastapi import FastAPI
from app.routers import clients, orders
app = FastAPI(
title="Clothing Orders API",
description="Order management system for your SME",
version="1.0.0"
)
app.include_router(clients.router, prefix="/clients", tags=["Clients"])
app.include_router(orders.router, prefix="/orders", tags=["Orders"])
With include_router, you separate responsibilities. Each router handles one resource. Code stays readable and testable.
How does validation with Pydantic work?
Pydantic defines classes that describe the shape of data. FastAPI automatically uses them to validate request body, query parameters, and path parameters.
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
from datetime import date
class ClientCreate(BaseModel):
name: str = Field(..., min_length=2, max_length=100)
email: EmailStr
phone: Optional[str] = None
birth_date: Optional[date] = None
class ClientResponse(BaseModel):
id: int
name: str
email: EmailStr
In the router, use the model as a type:
Sponsored Protocol
from fastapi import APIRouter, HTTPException, status
from app.schemas import ClientCreate, ClientResponse
router = APIRouter()
@router.post("/", response_model=ClientResponse, status_code=status.HTTP_201_CREATED)
async def create_client(client: ClientCreate):
# client is already validated: name not empty, email valid, etc.
# Here you save to DB, send email, etc.
return ClientResponse(id=1, name=client.name, email=client.email)
Real benefit: if a client sends a request with an invalid 'email', FastAPI responds with a 422 error and a clear description. No crashes, no late-night debugging.
Real-world example: validation error handling
Imagine a POST with a wrong JSON body. FastAPI returns:
{
"detail": [
{
"loc": ["body", "email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}
This is gold for developers and the frontend. Zero extra code to write.
How to leverage automatic documentation (Swagger and Redoc)?
FastAPI automatically generates two interactive documentation interfaces: /docs (Swagger UI) and /redoc (Redoc). You don’t have to write a single line of OpenAPI. Your clients or technical partners can test endpoints directly from the browser. For an SME that needs to integrate with suppliers or consultants, this cuts onboarding time by 50%.
Sponsored Protocol
Just start the app with uvicorn app.main:app --reload and open http://localhost:8000/docs. Every Pydantic model, every parameter, every error response is documented. If you change a schema, the docs update automatically.
Pro tip: customize the title and description in the app metadata (as we did above). Clients appreciate a professional message instead of "FastAPI".
How to handle errors and status codes professionally?
An API is not just 200 responses. It must clearly communicate what went wrong. FastAPI makes it easy to raise standard HTTP exceptions:
from fastapi import HTTPException, status
@router.get("/{client_id}")
async def get_client(client_id: int):
client = fake_db.get(client_id)
if not client:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Client not found"
)
return client
You can also create global handlers to catch unexpected errors:
from fastapi import Request
from fastapi.responses import JSONResponse
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
return JSONResponse(
status_code=400,
content={"message": "Invalid data: " + str(exc)}
)
We always do this: it lets you return messages in the client’s language and hide internal details in production. Cybersecurity starts here: never expose real stack traces in a production environment.
Sponsored Protocol
What to do now?
You don’t need a powerful server to start. Get a €10/month VPS (or run locally), install Python 3.10+, create a virtual environment, and write your first endpoint. Follow these steps:
- Create the project: folder structure as shown above.
- Install FastAPI and Uvicorn:
pip install fastapi uvicorn - Write a Pydantic model for your domain (e.g., Product, Order, Client).
- Define a router with basic CRUD operations.
- Start the server with
uvicorn app.main:app --reloadand test at /docs. - Add logging and error handling as shown above.
- Deploy to production: use a reverse proxy (Nginx + supervisor/systemd or Docker).
If you want to dive into database integration, check out our guide on the Python for Developers pillar. There you’ll find SQLAlchemy, Tortoise ORM, and FastAPI integration.
Remember: an API is not finished until it documents itself and handles errors like a competent human. FastAPI gives you both for free. Use them.