How to Build a REST API with FastAPI in 2026

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.

Key Takeaway

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 dict parsing.
  • 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 pytest and pytest-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.

Leave a Reply

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