A comprehensive guide to create first FastAPI Rest API

Abhijeet kumar
6 min read5 days ago

--

In this blog post, we’ll explore how to create a simplified FastAPI project using SQLAlchemy ORM and an SQLite database to manage a basic newsletter subscription system, including subscribing, unsubscribing, and retrieving the subscriber list.

  1. Create Virtual Enviromint and FastAPI project —

After creating our project directory we need to be create virtual environment then we will install required library by executing below command.

pip install fastapi[standard] uvicorn sqlalchemy aiomysql pydantic_settings 
  • fastapi[standard] — Installs FastAPI along with extra dependencies for full functionality (like pydantic, httpx).
  • uvicorn — ASGI server for running FastAPI applications efficiently.
  • sqlalchemy — ORM for interacting with relational databases using Python.
  • aiomysql — Asynchronous MySQL driver for SQLAlchemy to enable async database operations.
  • pydantic_settings — Manages application settings using .env files and environment variables with Pydantic, then generate a requirements.txt file which hold all dependencies details.
pip freeze > requirements.txt

Once installation is completed then we have to create a main.py which is responsive for manage our FastAPI project, over all structure will look like this.

my_fastapi_project/
├── main.py
├── database/
│ ├── config.py
│ ├── database.py
├── newsletter/models.py
│ ├── models.py
│ ├── routes.py
│ ├── schemas.py
│ ├── utils.py
├── .env
├── requirements.txt
├── venv/ (virtual environment)
└── vercel.json
├── .gitignore
└── README.md

Now let break down each file importance in details.

  1. main.py —

In FastAPI, main.py is the entry point of the application where the FastAPI instance is created, routes (endpoints) are defined, and the app is run using a server like Uvicorn.

It serves as the starting file for handling API requests and responses.



from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from database.database import get_db , engine , Base
import os
import uvicorn
from newsletter.routes import router as newsletter_route
app = FastAPI()
Base.metadata.create_all(bind=engine)
app.include_router(newsletter_route)

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True,
reload_dirs=[os.path.dirname(os.path.abspath(__file__))],
reload_excludes=[
"*/.git/*",
"*/__pycache__/*",
"*.pyc",
"*/.pytest_cache/*",
"*/.vscode/*",
"*/.idea/*"
],
reload_delay=1,
reload_includes=["*.py", "*.html", "*.css", "*.js"]
)

Break down —

This FastAPI application is structured to handle newsletters and is configured with middleware, database connections, and automatic reloading. Below is a breakdown of each component.

  1. Imports —
  • FastAPI— The core framework for building APIs.
  • CORSMiddleware— Middleware to handle Cross-Origin Resource Sharing (CORS).
  • get_db, engine, Base— Database-related imports from a custom module.
  • os— Used for handling file paths.
  • uvicorn— ASGI server to run the FastAPI application.
  • router as newsletter_route— Imports newsletter-related routes.

2. Application Initialization —

  • app = FastAPI()— Creates an instance of the FastAPI application.

3. Database Setup:

  • Base.metadata.create_all(bind=engine)— Ensures all database tables are created using SQLAlchemy's ORM.

4. Including Routers —

  • app.include_router(newsletter_route) — Registers the newsletter routes with the FastAPI app.

5. CORS Middleware Configuration —

  • allow_origins=["*"]— Allows requests from any origin.
  • allow_credentials=True— Allows sending credentials (cookies, authorization headers).
  • allow_methods=["*"] — Allows all HTTP methods.
  • allow_headers=["*"]— Allows all headers.

6. Application Execution —

  • The __main__ block ensures that the application runs when executed directly.
  • uvicorn.run("main:app", ...) starts the FastAPI server with
  • host="0.0.0.0": Listens on all network interfaces.
  • port=8000— Runs on port 8000.
  • reload=True — Enables automatic reload on code changes.
  • reload_dirs — Specifies which directories to watch for changes.
  • reload_excludes— Excludes unnecessary files and directories from reloading.
  • reload_includes — Specifies file types to trigger reloads.

2. config.py —

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
DATABASE_URL: str
class Config:
env_file = ".env"

settings = Settings()

This code defines a Pydantic Settings class to manage environment variables. It inherits from BaseSettings, allowing automatic loading of configuration values from an .env file or system environment variables.

The Config subclass specifies the .env file as the source. When an instance of Settings is created, it fetches the environment variables, enabling secure and flexible configuration management.

3. database.py —

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .config import settings

SQLALCHEMY_DATABASE_URL = settings.DATABASE_URL

#ensure .env file with DATABASE_URL="database_name" or put static database name

engine = create_engine(
SQLALCHEMY_DATABASE_URL,
connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

This code sets up SQLAlchemy with a database connection using environment-based configuration.

  1. Import necessary SQLAlchemy components
  • create_engine— Establishes a connection to the database.
  • declarative_base— Creates a base class for defining ORM models.
  • sessionmaker — Manages database sessions.

2. Load database URL dynamically

  • It imports settings from config to fetch DATABASE_URL from environment variables.

3. Create the database engine

  • create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) initializes a connection.
  • The connect_args parameter ensures SQLite allows multiple threads to interact with the database.

4. Session Management

  • SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) transactions variable.

5. Base Class for ORM Models

  • Base = declarative_base() is used to define database models.

6. Dependency for Database Session

  • get_db() provides a session instance for database operations using a generator.
  • It ensures that the session is properly closed after use.

4. models.py —

 
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from database.database import Base
from datetime import datetime
class NewsletterSubscription(Base):
__tablename__ = "newsletter_subscriptions"
id = Column(Integer, primary_key=True, index=True)
email = Column(String(255) , unique=True , nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)

This is Newsletter Table model with id , email , is_active , create_at columns.

5. routes.py —

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from database.database import get_db
from .schemas import NewsletterSubscriptionBase , NewsletterSubscriptionCreate , NewsletterSubscriptionResponse
from .utils import create_subscription , get_all_subscriptions , delete_subscription
router = APIRouter(prefix="/newsletter", tags=["newsletter"])

@router.post("/subscribe", response_model=NewsletterSubscriptionResponse)
def subscribe(
subscription: NewsletterSubscriptionCreate,
db: Session = Depends(get_db)
):

return create_subscription(db, subscription)

@router.get("/subscriptions", response_model=list[NewsletterSubscriptionResponse])
def get_subscriptions(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
return get_all_subscriptions(db, skip, limit)

@router.delete("/unsubscribe/{email}")
def unsubscribe(email: str, db: Session = Depends(get_db)):
if delete_subscription(db, email):
return {"message": "Successfully unsubscribed"}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Subscription not found"
)

This file routes.py perfoming actions while its using utils function by passing parameter in functions.

  • subscribe (POST /subscribe) — Adds a new subscription to the newsletter route using utils function for subscribe.
  • get_subscriptions (GET /subscriptions) — Retrieves a list of all newsletter subscriptions with optional pagination (skip and limit) route using utils function for subscribe.
  • unsubscribe (DELETE /unsubscribe/{email}) — Removes a subscription using an email; returns an error if the email is not found route using utils function for subscribe.

6. schemas.py —

from pydantic import BaseModel, EmailStr
from datetime import datetime
from typing import Optional

class NewsletterSubscriptionBase(BaseModel):
email: EmailStr

class NewsletterSubscriptionCreate(NewsletterSubscriptionBase):
pass

class NewsletterSubscriptionResponse(NewsletterSubscriptionBase):
id: int
is_active: bool
created_at: datetime

class Config:
from_attributes = True

This setup ensures data validation, serialization, and deserialization, making it useful in APIs (e.g., FastAPI) for handling newsletter subscriptions efficiently.

Let break down —

  • BaseModel — The base class for creating Pydantic models.
  • EmailStr — A built-in type from Pydantic that validates email formats.
  • datetime — Used for handling timestamps.
  • Optional — Allows fields to be optional.
  1. Define a Base Model (NewsletterSubscriptionBase)
  • Contains a single field, email, which must be a valid email address (EmailStr).
  • This acts as a base class for other models to inherit.

2. Define a Create Model (NewsletterSubscriptionCreate)

  • Inherits from NewsletterSubscriptionBase.
  • No additional fields, meaning it has the same structure as the base model.
  • Used specifically for handling user input when subscribing.

3. Define a Response Model (NewsletterSubscriptionResponse)

  • Extends NewsletterSubscriptionBase to include additional fields:
  • id — A unique identifier for the subscription.
  • is_active — A boolean flag indicating if the subscription is active.
  • created_at— A timestamp showing when the subscription was created.
  • The Config class includes:
  • from_attributes = True— Allows automatic conversion from ORM models to Pydantic models.

7. utils.py —

from sqlalchemy.orm import Session
from . import models, schemas
from fastapi import HTTPException, status

def create_subscription(db: Session, subscription: schemas.NewsletterSubscriptionCreate):
db_subscription = models.NewsletterSubscription(
email=subscription.email
)
try:
db.add(db_subscription)
db.commit()
db.refresh(db_subscription)
return db_subscription
except Exception as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already subscribed"
)

def get_subscription(db: Session, email: str):
return db.query(models.NewsletterSubscription).filter(
models.NewsletterSubscription.email == email
).first()

def get_all_subscriptions(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.NewsletterSubscription).offset(skip).limit(limit).all()

def delete_subscription(db: Session, email: str):
subscription = get_subscription(db, email)
if subscription:
db.delete(subscription)
db.commit()
return True
return False

These utility functions are responsible for handling subscription creation, retrieval by email, listing, and deletion.

  • create_subscription — Adds a new email subscription to the database. If the email is already subscribed, it rolls back the transaction and raises an error.
  • get_subscription — Retrieves a subscription by email.
  • get_all_subscriptions — Fetches all subscriptions with optional pagination (skip and limit).
  • delete_subscription — Deletes a subscription by email if it exists; returns False if not found.

Conclusion: FastAPI makes building APIs fast, efficient, and developer-friendly with automatic validation, type hints, and asynchronous support.

By following this guide, you’ve set up your first FastAPI application, defined endpoints, and tested them using the interactive Swagger UI.

With its simplicity and performance, FastAPI is a great choice for modern web APIs.

Your appreciation or feedback on this insight would be greatly valued, whether it’s a small follow up or leaving a note.

And connect with me on X handler for the tech insights.
Looking forward to engaging with you!

--

--

No responses yet