ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-03-31Beginner

Antigravity × Python FastAPI — Build RESTful APIs Fast with AI Agents

Learn how to use Antigravity's AI agents to build a Python FastAPI backend from scratch. Covers endpoint design, JWT authentication, database integration, and deployment with practical code examples.

python26fastapi2backend3rest-apiapp-dev49

Setup and context — Why Antigravity + Python FastAPI?

Python consistently ranks among the most widely used programming languages, and its popularity in backend development continues to grow. Within that ecosystem, FastAPI has become a go-to framework thanks to its three standout traits: type safety, high performance, and automatic API documentation.

Combine FastAPI with Antigravity's AI agents and you can move from idea to deployed API at a pace that previously required an entire team. This guide walks you through building a complete FastAPI backend — CRUD endpoints, JWT authentication, database integration, and cloud deployment — using Antigravity as your AI coding partner.

What you'll learn:

  • Bootstrapping a Python FastAPI project from zero with Antigravity
  • Writing effective prompts to get the most out of AI agents
  • Implementing CRUD endpoints, JWT auth, and SQLite/PostgreSQL integration
  • Deploying to Cloudflare Workers, Railway, or Render

This guide assumes you know basic Python syntax and have tried Antigravity at least once (Planning Mode or agent chat).


Setup — Python + FastAPI + Antigravity

Prerequisites

Make sure the following tools are installed before you start:

  • Python 3.11 or later (python --version)
  • pip or uv for fast package management
  • Antigravity (latest version)
  • Git

Project Initialization

Create a new directory, set up a virtual environment, and install the core dependencies:

mkdir my-fastapi-app && cd my-fastapi-app
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install fastapi uvicorn[standard] sqlmodel pydantic python-jose[cryptography]

Open the project in Antigravity, then switch to Planning Mode and prompt it to scaffold the architecture:

Create a Python FastAPI project in this directory.
Features: user management CRUD, JWT authentication, SQLite integration.
Please suggest the folder structure as well.

Antigravity will propose a structure like this:

my-fastapi-app/
├── app/
│   ├── __init__.py
│   ├── main.py          # FastAPI application entry point
│   ├── models.py        # SQLModel / Pydantic models
│   ├── database.py      # DB connection and session management
│   ├── auth.py          # JWT authentication logic
│   └── routers/
│       ├── users.py     # User-related endpoints
│       └── items.py     # Item-related endpoints
├── tests/
│   └── test_main.py
├── requirements.txt
└── README.md

Implementing CRUD Endpoints

Defining Models and the Database

Ask Antigravity to generate app/models.py and app/database.py:

Create a user model in app/models.py using SQLModel.
Fields: id (int, primary key), username (str), email (str), hashed_password (str), created_at (datetime)

The generated code looks like this:

# app/models.py
from datetime import datetime
from typing import Optional
from sqlmodel import Field, SQLModel
 
class UserBase(SQLModel):
    username: str = Field(index=True, unique=True)
    email: str = Field(index=True, unique=True)
 
class User(UserBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    hashed_password: str
    created_at: datetime = Field(default_factory=datetime.utcnow)
 
class UserCreate(UserBase):
    password: str  # Plain-text password received at registration
 
class UserRead(UserBase):
    id: int
    created_at: datetime

Next, generate app/database.py:

# app/database.py
from sqlmodel import create_engine, Session, SQLModel
 
DATABASE_URL = "sqlite:///./app.db"
 
engine = create_engine(DATABASE_URL, echo=True)
 
def create_db_and_tables():
    """Create tables on startup."""
    SQLModel.metadata.create_all(engine)
 
def get_session():
    """DB session generator used with FastAPI's Depends."""
    with Session(engine) as session:
        yield session

User CRUD Router

Prompt Antigravity to implement the user router:

Create CRUD endpoints for users in app/routers/users.py.
Routes: GET /users, GET /users/{id}, POST /users, DELETE /users/{id}
Hash passwords with bcrypt before saving.
# app/routers/users.py
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select
from passlib.context import CryptContext
 
from app.database import get_session
from app.models import User, UserCreate, UserRead
 
router = APIRouter(prefix="/users", tags=["users"])
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
 
@router.get("/", response_model=list[UserRead])
def list_users(session: Session = Depends(get_session)):
    """Return all users."""
    return session.exec(select(User)).all()
 
@router.get("/{user_id}", response_model=UserRead)
def get_user(user_id: int, session: Session = Depends(get_session)):
    """Return a user by ID."""
    user = session.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user
 
@router.post("/", response_model=UserRead, status_code=201)
def create_user(user_data: UserCreate, session: Session = Depends(get_session)):
    """Create a new user."""
    hashed_pw = pwd_context.hash(user_data.password)
    user = User(
        username=user_data.username,
        email=user_data.email,
        hashed_password=hashed_pw,
    )
    session.add(user)
    session.commit()
    session.refresh(user)
    return user
 
@router.delete("/{user_id}", status_code=204)
def delete_user(user_id: int, session: Session = Depends(get_session)):
    """Delete a user by ID."""
    user = session.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    session.delete(user)
    session.commit()

JWT Authentication

No production API is complete without authentication. Ask Antigravity to add JWT auth:

Implement JWT token generation and validation in app/auth.py.
Also add a login endpoint: POST /auth/token
# app/auth.py
import os
from datetime import datetime, timedelta
from typing import Optional
 
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlmodel import Session, select
 
from app.database import get_session
from app.models import User
 
SECRET_KEY = os.environ.get("SECRET_KEY", "change-this-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60
 
router = APIRouter(prefix="/auth", tags=["auth"])
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
 
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """Generate a JWT access token."""
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
 
async def get_current_user(
    token: str = Depends(oauth2_scheme),
    session: Session = Depends(get_session),
) -> User:
    """Retrieve the currently authenticated user."""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Invalid 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
 
    user = session.exec(select(User).where(User.username == username)).first()
    if user is None:
        raise credentials_exception
    return user
 
@router.post("/token")
def login(
    form_data: OAuth2PasswordRequestForm = Depends(),
    session: Session = Depends(get_session),
):
    """Issue a JWT token for valid username/password credentials."""
    user = session.exec(select(User).where(User.username == form_data.username)).first()
    if not user or not pwd_context.verify(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
        )
    token = create_access_token(
        data={"sub": user.username},
        expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
    )
    return {"access_token": token, "token_type": "bearer"}

Wiring Everything Together in main.py

# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
 
from app.database import create_db_and_tables
from app.routers import users
from app import auth
 
@asynccontextmanager
async def lifespan(app: FastAPI):
    """Create DB tables on startup."""
    create_db_and_tables()
    yield
 
app = FastAPI(
    title="My FastAPI App",
    description="Antigravity × Python FastAPI sample",
    version="1.0.0",
    lifespan=lifespan,
)
 
app.include_router(users.router)
app.include_router(auth.router)
 
@app.get("/")
def root():
    return {"message": "Hello, FastAPI + Antigravity!"}

Start the server and verify everything works:

uvicorn app.main:app --reload
# → Visit http://localhost:8000/docs to see the auto-generated Swagger UI

Expected output: INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)


AI-Assisted Test Generation

Once the implementation is in place, let Antigravity write the tests:

Create pytest + httpx tests in tests/test_main.py.
Cover: user creation, user listing, and JWT login.
# tests/test_main.py
import pytest
from fastapi.testclient import TestClient
from sqlmodel import create_engine, Session, SQLModel
 
from app.main import app
from app.database import get_session
 
TEST_DATABASE_URL = "sqlite://"
 
@pytest.fixture(name="session")
def session_fixture():
    engine = create_engine(TEST_DATABASE_URL, connect_args={"check_same_thread": False})
    SQLModel.metadata.create_all(engine)
    with Session(engine) as session:
        yield session
 
@pytest.fixture(name="client")
def client_fixture(session: Session):
    def override_get_session():
        yield session
    app.dependency_overrides[get_session] = override_get_session
    yield TestClient(app)
    app.dependency_overrides.clear()
 
def test_create_user(client: TestClient):
    """Verify that user creation returns 201 and the correct data."""
    response = client.post("/users/", json={
        "username": "testuser",
        "email": "test@example.com",
        "password": "securepassword123"
    })
    assert response.status_code == 201
    data = response.json()
    assert data["username"] == "testuser"
    assert "hashed_password" not in data  # Confirm password is not exposed
 
def test_login(client: TestClient):
    """Verify that a valid login returns a JWT access token."""
    client.post("/users/", json={
        "username": "loginuser",
        "email": "login@example.com",
        "password": "password123"
    })
    response = client.post("/auth/token", data={
        "username": "loginuser",
        "password": "password123"
    })
    assert response.status_code == 200
    assert "access_token" in response.json()

Run the tests:

pip install pytest httpx
pytest tests/ -v
# Expected: 2 passed in 0.XXs

Deployment — Shipping to Railway or Render

Deploying to Railway

Railway makes Python backend deployment straightforward. Ask Antigravity to prepare the deployment files:

Update the Procfile and requirements.txt for Railway deployment.
Also create a .env.example to handle the SECRET_KEY safely.

Generated files:

# Procfile
web: uvicorn app.main:app --host 0.0.0.0 --port $PORT

# .env.example
SECRET_KEY=your-secret-key-here
DATABASE_URL=sqlite:///./app.db

Deploy in a few commands:

npm install -g @railway/cli
 
railway login
railway init
 
railway variables set SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
 
railway up

To take your API security further, check out Antigravity エージェントでセキュアな API バックエンドを構築する — 認証・バリデーション・レート制限の実装パターン, which dives deep into authentication patterns, request validation, and rate limiting strategies.


Common Errors and Fixes

Here are the most frequent issues you'll encounter when building FastAPI apps with Antigravity, along with their solutions.

Error 1: ImportError: No module named 'app'

# Wrong — running the module directly
python app/main.py
 
# Correct — use uvicorn with the module path
uvicorn app.main:app --reload

Error 2: sqlalchemy.exc.OperationalError: no such table

Check that create_db_and_tables() is being called inside the lifespan context. If your code uses the deprecated @app.on_event("startup") decorator, migrate to the lifespan approach supported in FastAPI 0.93+.

Error 3: 422 Unprocessable Entity

A request body type mismatch is the usual culprit. Tell Antigravity: "Look at the Swagger UI at /docs and identify the validation error." The agent will inspect your Pydantic model and highlight the mismatch.


Summary

Pairing Antigravity with Python FastAPI creates a powerful workflow for backend development. Here's a recap of what we covered:

  • Project setup: Use Planning Mode to design the folder structure, then let the agent scaffold the boilerplate.
  • CRUD implementation: SQLModel + FastAPI delivers type-safe CRUD endpoints with minimal code.
  • JWT authentication: python-jose + passlib provide production-ready auth that Antigravity can generate in seconds.
  • Test generation: pytest + httpx test suites are written automatically by the agent.
  • Deployment: Railway and Render deployments are guided step-by-step by the AI.

Antigravity's real strength lies not just in generating code, but in proposing architecture — the how behind the what. Give it a try on your next Python FastAPI project today.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-07-13
The Gate That Stops Visual Damage Before You Hand Bulk Image Optimization to an Agent
When an agent bulk re-encoded a few hundred wallpaper assets, a handful came back with dulled color. Size-reduction alone cannot catch that. Here is how to design a gate that stops bad conversions before merge using three axes — SSIM, ΔE, and file size — with a checker that runs on Pillow and scikit-image.
App Dev2026-07-09
Before an Agent's .proto Edit Silently Breaks Binary Compatibility: Gating Wire Compat with buf breaking
When an agent edits your .proto files, the text can look perfect while wire compatibility quietly breaks. Here is how to stop field-number reuse and unsafe type changes with a working gate built from buf breaking and reserved.
App Dev2026-07-04
Paid, but the Ads Won't Go Away — Closing a StoreKit 2 Transaction.updates Launch Race with Antigravity
How I traced a StoreKit 2 launch race that dropped transactions arriving right after startup, pinned the listener to the app's lifetime instead of a view's, reconciled with currentEntitlements, and let Antigravity turn it into a StoreKitTest regression suite.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →