When you sit down to build a REST API with FastAPI, you probably want something that works fast, scales well, and doesn’t make you write a ton of boilerplate. FastAPI delivers on all those fronts. In 2026, it’s still the go-to framework for Python developers who need high performance and a clean developer experience. This tutorial walks you through building a production-ready REST API from scratch, using the latest tools and patterns.
By the end of this guide you will have a working REST API with CRUD endpoints, async database access using SQLAlchemy 2.0, request validation via Pydantic v2, JWT authentication, and error handling. You will also learn how to structure your project for maintainability and test it with pytest-asyncio. Every step includes real code you can copy and adapt.
Why FastAPI Remains a Top Choice in 2026
FastAPI isn’t new, but it keeps improving. The 2026 ecosystem includes Pydantic v2 for even stricter type validation, native async support that plays nicely with modern databases, and automatic OpenAPI documentation that saves you hours of manual writing. If you are building microservices, machine learning backends, or any API that needs to handle many concurrent requests, FastAPI gives you a solid foundation.
The framework also integrates well with other modern tools. You can pair it with async SQLAlchemy for database access, use dependency injection for clean service layers, and deploy it with Docker or serverless platforms. This tutorial focuses on the core steps to set up all of that.
What You Will Need
Before we write any code, make sure you have the following:
- Python 3.11 or higher installed
- A text editor or IDE (VS Code recommended)
- Basic understanding of Python and HTTP methods
- Postman, curl, or any HTTP client for testing
If you are newer to Python web development, you might want to first read about mastering NoSQL databases for modern web applications to see how they compare with SQL databases for APIs.
Step by Step: Build Your REST API
I will walk you through each step in a logical order. Follow along and you will have a running API in less than an hour.
1. Set Up Your Project and Install Dependencies
Create a new directory and a virtual environment.
mkdir fastapi-tutorial
cd fastapi-tutorial
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
Now install the core packages.
pip install fastapi uvicorn sqlalchemy asyncmy pydantic python-jose passlib[bcrypt] python-multipart
Here is what each package does:
- fastapi: the framework itself
- uvicorn: ASGI server to run your app
- sqlalchemy and asyncmy: async database access for MySQL (swap for asyncpg if using Postgres)
- pydantic: data validation (v2 is included by default)
- python-jose: JWT token creation and validation
- passlib: password hashing
- python-multipart: needed for form data (used in OAuth2 password flow)
2. Structure Your Application
A clean project layout helps as your API grows. Create these files:
myapi/
__init__.py
main.py
database.py
models.py
schemas.py
auth.py
routers/
__init__.py
items.py
users.py
The routers folder keeps endpoints organized. Each router handles a group of related routes.
3. Configure the Database
In database.py, set up the async engine and session.
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker, declarative_base
DATABASE_URL = "mysql+asyncmy://user:password@localhost/dbname"
engine = create_async_engine(DATABASE_URL, echo=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
Base = declarative_base()
async def get_db():
async with AsyncSessionLocal() as session:
yield session
Replace the connection string with your own credentials. For PostgreSQL, use postgresql+asyncpg://...
4. Define Your Data Models
In models.py, create SQLAlchemy models. Let’s build a simple item catalog.
from sqlalchemy import Column, Integer, String, Float, Boolean
from .database import Base
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
price = Column(Float, nullable=False)
in_stock = Column(Boolean, default=True)
5. Create Pydantic Schemas
In schemas.py, define request and response models.
from pydantic import BaseModel
class ItemCreate(BaseModel):
name: str
price: float
in_stock: bool = True
class ItemResponse(ItemCreate):
id: int
class Config:
from_attributes = True
The from_attributes config lets Pydantic read data from ORM objects.
6. Implement Authentication
In auth.py, set up JWT token handling and password hashing.
from datetime import datetime, timedelta, timezone
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
return username
For a real app you would store user credentials in a database. This example uses a hardcoded user for simplicity.
7. Build Router Endpoints
In routers/items.py, create CRUD endpoints.
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from ..database import get_db
from ..models import Item
from ..schemas import ItemResponse, ItemCreate
from sqlalchemy import select
router = APIRouter(prefix="/items", tags=["items"])
@router.post("/", response_model=ItemResponse, status_code=201)
async def create_item(item: ItemCreate, db: AsyncSession = Depends(get_db)):
db_item = Item(**item.dict())
db.add(db_item)
await db.commit()
await db.refresh(db_item)
return db_item
@router.get("/{item_id}", response_model=ItemResponse)
async def read_item(item_id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(Item).where(Item.id == item_id))
item = result.scalar_one_or_none()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
return item
Add similar endpoints for update and delete. Use dependency injection to get the database session.
8. Wire Everything in main.py
from fastapi import FastAPI
from .routers import items, users
from .database import engine, Base
app = FastAPI(title="My FastAPI REST API", version="1.0.0")
# Create tables on startup (for development only)
@app.on_event("startup")
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
app.include_router(items.router)
app.include_router(users.router)
@app.get("/")
def root():
return {"message": "Hello, 2026 API!"}
9. Run the API
uvicorn myapi.main:app --reload
Visit http://localhost:8000/docs to see the interactive Swagger UI. Test your endpoints there.
Best Practices for a Production API
Follow these guidelines to make your API reliable and maintainable.
- Use environment variables for secrets and database URLs. Never hardcode them.
- Structure routers by domain (users, items, orders). Keep each router file under 200 lines.
- Always validate inputs with Pydantic schemas. Avoid raw
dictparsing. - Use async wisely. Not every endpoint needs async. Use async for I/O bound tasks; keep CPU bound tasks synchronous.
- Add logging early. FastAPI integrates with Python’s logging module.
- Write tests with
pytestandpytest-asyncio. Test every endpoint. - Rate limit your API in production. Use middleware or a gateway.
For more on choosing the right tools, check out top open source frameworks every web developer should know in 2026.
Common Mistakes and How to Avoid Them
The table below shows frequent pitfalls when building FastAPI REST APIs and the correct approaches.
| Mistake | Why It Hurts | Better Approach |
|---|---|---|
| Using synchronous database sessions | Blocks the event loop, kills performance | Use async engines and sessions with async with |
| Ignoring Pydantic validation | Exposes API to malformed data | Always define request/response schemas |
| Hardcoding secrets in source code | Security risk in any shared repo | Store in .env and use python-dotenv |
| No error handling on endpoints | Generic 500 errors confuse clients | Raise HTTPException with specific status codes |
| Mixing business logic in route functions | Hard to test and maintain | Use dependency injection for services |
Expert Advice: “Start with a small, working API and add features incrementally. Do not try to build the perfect architecture on day one. Refactor as your understanding grows.” – Sarah Chen, Senior Backend Engineer
If you want to see how async patterns are applied in other languages, reading about mastering asynchronous programming in JavaScript for better performance can give you a fresh perspective.
Testing Your API Endpoints
Write a simple test for the create item endpoint. Create a tests folder with test_items.py.
import pytest
from httpx import AsyncClient
from myapi.main import app
@pytest.mark.asyncio
async def test_create_item():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.post("/items/", json={"name": "Test Item", "price": 9.99})
assert response.status_code == 201
data = response.json()
assert data["name"] == "Test Item"
Run tests with pytest. The httpx library is needed for the async test client.
Taking It Further
Once you have the basics, consider adding:
- Background tasks for sending emails or processing files
- CORS middleware to allow frontend apps to connect
- WebSocket endpoints for real-time features
- Database migrations with Alembic
- Containerization with Docker
Learning about database indexing techniques for faster queries can help optimize your API when it scales.
Your API Is Ready for 2026
Building a REST API with FastAPI does not have to be complicated. You now have a working API with async database access, input validation, authentication, and a clean project structure. Each of these pieces can be extended or swapped out as your project grows.
Take what you built here and adapt it to your own domain. Add more models, more routes, and more business logic. The patterns you learned will serve you well whether you are building a small backend for a personal project or a microservice for a large team.
Open your terminal, fire up your server, and start creating endpoints. Your 2026 API is only a few keystrokes away.