Table of Contents
Build a clean, professional backend from scratch using FastAPI, PostgreSQL, and SQLAlchemy — with virtual environments, requirements, and working code you can run today.
Introduction
A backend is more than just a whole lot of code — it is what actually drives your application.
With FastAPI and PostgreSQL, you can construct an engine that is fast, scalable, and surprisingly easy to understand.
But Why FastAPI?
We are using FastAPI for our backend because it is one of the most modern and efficient Python frameworks available.
Here is why it shines:
- High Performance: Built on Starlette and Pydantic, FastAPI is extremely fast (on par with Node.js and Go).
- Automatic Documentation: It automatically generates interactive API documentation (Swagger UI) at
/docs, saving hours of frontend coordination. - Type Safety: It uses standard Python type hints, which reduces bugs and improves editor support (autocomplete).
- Async Support: It handles asynchronous code natively, which is great for database connections and heavy loads.
In this tutorial, we will explain the anatomy of a backend and give you the exact code to start with.
The High-Level Picture
Before writing code, it is important to understand where everything goes.
A great backend is like a layer cake:
- Presentation Layer: The “Front Door” (Routes & Schemas).
- Data Layer: The “Storage” (Database & Models).
Let’s take it layer by layer.
Step 1: Setting Up Your Development Environment
Before writing any code, you should set up a clean Python environment using a
virtual environment (venv). This isolates your project dependencies from your system Python.
Why Create a Virtual Environment?
A virtual environment is a self-contained Python workspace. It helps you:
- Keep project dependencies separate from your system Python.
- Avoid version conflicts between different projects.
- Share your project easily with teammates using
requirements.txt. - Maintain a clean, reproducible development setup.
Create a Virtual Environment
Open your terminal in the project folder and run:
python -m venv venv
This creates a folder called venv with an isolated Python environment.
Activate the Virtual Environment
On macOS/Linux:
source venv/bin/activate
On Windows:
venv\Scripts\activate
Once activated, you should see (venv) at the beginning of your terminal prompt.
Create requirements.txt
The requirements.txt file lists all the Python packages your project needs.
Create a file named requirements.txt in your project root and add:
fastapi==0.115.0
uvicorn==0.30.0
sqlalchemy==2.0.36
psycopg2-binary==2.9.10
pydantic==2.9.2
python-multipart==0.0.6
What Each Package Does:
- fastapi: The web framework for building APIs.
- uvicorn: The ASGI server that runs your FastAPI app.
- sqlalchemy: The ORM library for database interactions.
- psycopg2-binary: PostgreSQL adapter for Python.
- pydantic: Data validation using Python type hints.
- python-multipart: Handles file uploads and form data.
Install Dependencies
With your virtual environment activated, run:
pip install -r requirements.txt
This installs all the packages with the specified versions.
Step 2: Setting Up the Foundation
First, we need a basic FastAPI application that acts as our server.
The Code (main.py)
from fastapi import FastAPI
app = FastAPI()
@app.get("/s")
def read_root():
return {"message": "Hello fastApi"}
This creates a web server that listens for requests and responds with a simple JSON message.
Step 3: The Data Layer – Connecting to PostgreSQL
Most backends are database-driven at the heart.
We will use SQLAlchemy to easily maintain this connection.
The Database Setup (db.py)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
# Update this with your actual database settings
DATABASE_URL = "postgresql://user:password@localhost:5432/mydb"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
Base = declarative_base()
Key Concept: SessionLocal is a factory that provides a new
database session for each request, protecting our data and keeping it organized.
Step 4: Defining the Data Structure (Models & Schemas)
We need to tell Python how our data is shaped. We do this in two ways:
- SQLAlchemy models (database layer).
- Pydantic schemas (API layer).
The Database Model (models.py)
from sqlalchemy import Column, Integer, String
from .db import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True)
name = Column(String)
The API Schema (schemas.py)
from pydantic import BaseModel
class UserCreate(BaseModel):
email: str
name: str
class UserRead(BaseModel):
id: int
email: str
name: str
class Config:
orm_mode = True
Why two files?
User(model) represents the raw database table.UserCreateandUserRead(schemas) are strict filters for data entering and leaving the API.
Step 5: The Presentation Layer – Creating the Endpoint
Now let’s bring it all together with a route.
This is where the API receives data, validates it, and saves it.
The Route Logic (main.py)
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from . import models, schemas
from .db import engine, SessionLocal
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency to get a DB session per request
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users", response_model=schemas.UserRead)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = models.User(email=user.email, name=user.name)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
What happens here?
- The client sends a JSON request with
emailandname. - Pydantic validates the data against
UserCreate. - The route receives a database session via
Depends(get_db). - A new
Useris created and saved to PostgreSQL. - The saved user (with its new
id) is returned as a JSON response.
Step 6: Project Structure – Keeping it Clean
As your project grows, you don’t want everything in a single file.
A proper structure looks like:
app/
├── main.py # Starts the app and routes
├── db.py # Database connection
├── models.py # Database tables
└── schemas.py # Pydantic validation
requirements.txt # List of dependencies
.gitignore
venv/ # Virtual environment (add this folder to .gitignore)
Step 7: How to Run It
You will use an ASGI server called Uvicorn to run your app.
Install Uvicorn (already in requirements)
If not installed yet and you want to install manually:
pip install uvicorn
Run the App
Make sure your virtual environment is activated and you are in the project root. Then run:
uvicorn app.main:app --reload
What this command does:
app.mainpoints to themain.pyfile inside theappfolder.:appis the FastAPI instance (app = FastAPI()).--reloadrestarts the server automatically when you make code changes (great for development).
Check the Docs
Open your browser and go to
http://127.0.0.1:8000/docs
FastAPI automatically generates an interactive UI (Swagger UI) where you can test your API.
Try creating a user by clicking the POST /users endpoint and hitting “Try it out”.
Summary
You have now built a scalable backend foundation:
- Created a virtual environment and installed dependencies from
requirements.txt. - Connected PostgreSQL using SQLAlchemy.
- Defined data with Models (database tables) and Schemas (API validation).
- Created an API route to save users securely.
- Set up a clean project structure that is ready to grow.
- Ran it locally using Uvicorn and tested it in FastAPI’s interactive docs.
This setup is clean, professional, and ready for future features like authentication, error handling, and more complex business logic.