Beginner’s Guide for FastAPI

Build a CRUD API

A Complete Beginner-Friendly Walkthrough notes

This guide is based on the FastAPI crash course from Telusko’s YouTube channel (1.5 hours) and walks through building a complete Product Inventory CRUD API using FastAPI, PostgreSQL, SQLAlchemy, and React.

If you’ve been wanting to start with FastAPI but don’t know where to begin, this post will help you go step by step — starting from a blank folder and ending with a fully working API connected to a real database.

📌 Video Reference: https://youtu.be/Lu8lXXlstvM?si=h13a8hQk7EWTxKR3

Git repo: https://github.com/iamharisai/Product-Inventory-Trac


What You’ll Learn

  • What FastAPI is and why developers love it
  • How to set up a backend project with FastAPI
  • How to create API routes for CRUD operations
  • Using Pydantic for clean, automatic data validation
  • Wiring your API to PostgreSQL with SQLAlchemy
  • Setting up a minimal React frontend to view results
  • Using Swagger UI for testing your API

These notes are designed so you can follow along or come back later and quickly set everything up again.


Tools Used

  • FastAPI – Backend framework
  • Uvicorn – ASGI server
  • React – Frontend UI
  • PostgreSQL – Database
  • Python – Programming language
  • VS Code – Editor

What Is FastAPI?

FastAPI is a modern, high-performance Python framework built around type hints and asynchronous programming.
It works on top of:

  • Starlette for request handling
  • Pydantic for data validation

Why it stands out:

  • Extremely fast
  • Minimal boilerplate
  • Auto-generated API docs at /docs
  • Ideal for microservices, small apps, prototypes, and ML inference

This project is the perfect way to understand FastAPI’s workflow.


Setting Up the React Frontend

Set up the folder for this project, copy the frontend folder from git Frontend.

install dependencies:

cd frontend
npm install
npm start

This will create a folder called node_modules in your frontend directory and install packages from package.json

The React UI loads instantly. At this point, it won’t display any products yet — because our backend doesn’t serve anything. Let’s fix that.


Backend Setup

Create a virtual environment and activate it:

python3 -m venv .venv
source .venv/bin/activate

Install FastAPI and Uvicorn:

pip install fastapi uvicorn

Create a simple main.py to verify everything works:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def greet():
    return "Hello, world!"

Run the server:

uvicorn main:app --reload --host 127.0.0.1 --port 8000

Now you have a working FastAPI app. Time to build real endpoints.


Creating an In-Memory Product List

Before connecting a proper database, we start with something simple: an in-memory list of products.

This helps us build and test CRUD operations without worrying about SQL or environment variables.

products = [
    Product(id=1, name='Mobile', description='Motorola sleek mobile', price=250, quantity=4),
    Product(id=2, name='Laptop', description='Dell Inspiron laptop', price=800, quantity=2),
    Product(id=3, name='Tablet', description='Apple iPad', price=600, quantity=3),
    Product(id=4, name='Smartwatch', description='Samsung Galaxy smartwatch', price=200, quantity=5)
]

But before we use this list, we need a model.


Defining Product Using Pydantic

Pydantic models define the shape of your data. FastAPI uses these models to:

  • Validate incoming data
  • Reject invalid requests automatically
  • Serialize data into JSON
  • Document your API

Create models.py:

from pydantic import BaseModel

class Product(BaseModel):
    id: int
    name: str
    description: str
    price: float
    quantity: int

Now FastAPI knows exactly what a Product looks like.


CRUD Endpoints (In-Memory Version)

Get all products

@app.get("/products/")
def get_all_products():
    return products

This returns the product list instantly.

Get a single product

@app.get("/products/{product_id}")
def get_product_by_id(product_id: int):
    for product in products:
        if product.id == product_id:
            return product
    return {"error": "Product not found"}

Create a product

@app.post("/products/")
def create_product(product: Product):
    products.append(product)
    return {"message": "Product created successfully", "product": product}

Update a product

@app.put("/products/{product_id}")
def update_product(product_id: int, product: Product):
    for i in range(len(products)):
        if products[i].id == product_id:
            products[i] = product
            return {"message": "Product updated", "product": product}
    return {"error": "Product not found"}

Delete a product

@app.delete("/products/{product_id}")
def delete_product(product_id: int):
    for i in range(len(products)):
        if products[i].id == product_id:
            del products[i]
            return {"message": "Product deleted"}
    return {"error": "Product not found"}

Everything works smoothly — but only in-memory.


The Limitation: No Persistence

If you restart the server:

  • All newly added products disappear
  • Data resets to the list we defined in the code

This is because Python stores the list in RAM. To fix this, we need a real database. Let’s wire up PostgreSQL.


Loading Environment Variables

We store DB secrets in .env and load them using pydantic-settings.

settings.py:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    DB_USER: str
    DB_PASS: str
    DB_HOST: str = "localhost"
    DB_PORT: int = 5432
    DB_NAME: str = "telusko"

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

settings = Settings()

This keeps credentials outside the code. Now install DB dependencies:

pip install sqlalchemy psycopg2

Setting Up Database Connection

Create database.py:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from settings import settings

DATABASE_URL = (
    f"postgresql://{settings.DB_USER}:{settings.DB_PASS}@"
    f"{settings.DB_HOST}:{settings.DB_PORT}/{settings.DB_NAME}"
)

engine = create_engine(DATABASE_URL)
session = sessionmaker(autocommit=False, autoflush=False, bind=engine)

This creates a reusable engine and database session factory.


Creating Database Tables with SQLAlchemy

from sqlalchemy import Column, Integer, Float, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Product(Base):
    __tablename__ = 'product'

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String)
    description = Column(String)
    price = Column(Float)
    quantity = Column(Integer)

Create the actual table:

database_models.Base.metadata.create_all(bind=engine)

Once this runs, the product table exists inside PostgreSQL.


Initializing the Database with Sample Products

To preload the initial set of products:

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

def init_db():
    db = session()
    count = db.query(database_models.Product).count()
    if count == 0:
        for product in products:
            db.add(database_models.Product(**product.model_dump()))
        db.commit()

init_db()

Notice how we convert:

Pydantic model → SQLAlchemy model

using:

**product.model_dump()

This makes both models play nicely together.

Connecting backend with frontend

As frontend is using 3000 port and backend is using 8000 port, we will get blocked by CORS policy

What is CORS, or Cross-Origin Resource Sharing? is a security mechanism in web browsers that allows a web page to make requests to a domain different from its own. It works by enabling servers to send special HTTP headers that tell the browser which cross-origin requests are permitted, thereby overriding the browser’s default same-origin policy which would otherwise block such requests. This allows for more flexible web applications that can, for example, use resources from third-party APIs or fonts. 

app = FastAPI()

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

Refactoring CRUD to Use the Database

At this point, every CRUD route must:

  • Accept DB session
  • Query SQLAlchemy models
  • Commit changes
  • Return responses

The complete version is available here:

🔗 GitHub Repo: https://github.com/iamharisai/Product-Inventory-Trac


Final Thoughts

This hands-on mini-project takes you through the essential FastAPI workflow:

  • Start with a simple in-memory prototype
  • Add real data validation with Pydantic
  • Build CRUD operations
  • Move to PostgreSQL for persistence
  • Use SQLAlchemy to map models
  • Connect the backend to a frontend

FastAPI makes the entire process clean, predictable, and surprisingly fast to build.

If you’re just getting started with backend development, this is a great foundation to build more real-world APIs.


Discover more from I am Harisai

Subscribe now to keep reading and get access to the full archive.

Continue reading