Build a clean, professional backend from scratch using FastAPI, PostgreSQL, and SQLAlchemy — with virtual environments, requirements, and working code you can run today.
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.
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:
/docs, saving hours of frontend coordination.In this tutorial, we will explain the anatomy of a backend and give you the exact code to start with.
Before writing code, it is important to understand where everything goes.
A great backend is like a layer cake:
Let’s take it layer by layer.
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.
A virtual environment is a self-contained Python workspace. It helps you:
requirements.txt.Open your terminal in the project folder and run:
python -m venv venv
This creates a folder called venv with an isolated Python 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.
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:
With your virtual environment activated, run:
pip install -r requirements.txt
This installs all the packages with the specified versions.
First, we need a basic FastAPI application that acts as our server.
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.
Most backends are database-driven at the heart.
We will use SQLAlchemy to easily maintain this connection.
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.
We need to tell Python how our data is shaped. We do this in two ways:
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)
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.UserCreate and UserRead (schemas) are strict filters for data entering and leaving the API.
Now let’s bring it all together with a route.
This is where the API receives data, validates it, and saves it.
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?
email and name.UserCreate.Depends(get_db).User is created and saved to PostgreSQL.id) is returned as a JSON response.
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)
You will use an ASGI server called Uvicorn to run your app.
If not installed yet and you want to install manually:
pip install uvicorn
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.main points to the main.py file inside the app folder.:app is the FastAPI instance (app = FastAPI()).--reload restarts the server automatically when you make code changes (great for development).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”.
You have now built a scalable backend foundation:
requirements.txt.This setup is clean, professional, and ready for future features like authentication, error handling, and more complex business logic.