What Rollercoaster of Code Awaits You in Building a Full-Stack Web App from Scratch?

A Journey Through FastAPI, React, and PostgreSQL: Building Your Dream Web Application

What Rollercoaster of Code Awaits You in Building a Full-Stack Web App from Scratch?

Imagine you’re about to embark on an exciting journey of building a full-stack web application. It’s a rollercoaster ride but in the best way possible. Combining FastAPI for the backend, React for the frontend, and PostgreSQL as your trusty database, you’re all set for turning your ideas into reality. Here’s a laid-back yet thorough walkthrough of how to build this tech marvel from scratch.

First things first, let’s get organized. Think of it as setting up your workbench before you start building something cool. Create a tidy structure that separates your frontend and backend duties. You’ll have two main directories - one for FastAPI and another for React.

mkdir fastapi-react-app
cd fastapi-react-app
mkdir fastapi
mkdir react

Backend Fun with FastAPI

Alright, time to dive into the backend. Start off by setting up a virtual environment for your FastAPI project. This ensures all the magic happens in its own bubble, without messing up your computer.

python3 -m venv fastapi/env
source fastapi/env/bin/activate

Next, you’ll need some key ingredients to make your backend delicious. Install these dependencies:

pip install fastapi uvicorn sqlalchemy psycopg2-binary

Cooking Up the Database

With PostgreSQL as your database, it’s like choosing the right kind of flour for baking the perfect bread. Ensure the PostgreSQL client library is installed, and create your database. Here’s how to handle database connections neatly in a database.py file inside your FastAPI directory:

from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker

SQLALCHEMY_DATABASE_URL = "postgresql://user:password@localhost/dbname"

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

Designing Models and Schemas

Ever tried assembling a piece of IKEA furniture? Defining database models using SQLAlchemy is similar. Create a models.py file that lays the groundwork for your app’s data structure:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from .database import Base

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True, index=True)
    hashed_password = Column(String)

Schemas are like blueprints. They ensure the data is shaped correctly as it flows through your app. Check out this example:

from pydantic import BaseModel

class UserBase(BaseModel):
    email: str

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int

    class Config:
        orm_mode = True

Crafting API Endpoints

Here’s where the magic happens. Create a main.py file to set up your FastAPI app and define routes. It’s like mapping out your treasure hunt:

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from . import models, schemas, database

app = FastAPI()

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

@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    db_user = models.User(email=user.email)
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    return db_user

Frontend Adventures with React

Swinging over to the frontend, create your React app using a handy tool called Create React App. Think of it as pulling out a pre-organized tool kit for your project.

npx create-react-app .

Building React Components

Craft your login and registration forms as React components. You’ll love working with state and event handlers, which keep your forms alive. Here’s an example for a login component:

import React, { useState } from 'react';
import axios from 'axios';

const Login = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      const response = await axios.post('http://localhost:8000/token', {
        grant_type: 'password',
        username: email,
        password: password,
      });
      localStorage.setItem('token', response.data.access_token);
      window.location.href = '/';
    } catch (error) {
      setError(error.response.data.detail);
    }
  };

  return (
    <div>
      <h1>Login</h1>
      <form onSubmit={handleSubmit}>
        <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" />
        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password" />
        <button type="submit">Login</button>
        {error && <p style={{ color: 'red' }}>{error}</p>}
      </form>
    </div>
  );
};

export default Login;

Nailing Authentication and Authorization

Authentication is like having a VIP pass. When users log in, generate JWT tokens and keep them safe in local storage. Authenticate subsequent requests using these tokens.

Backend Authentication

Protect routes using OAuth2PasswordBearer in your FastAPI app:

from fastapi.security import OAuth2PasswordBearer
from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.get("/users/me", response_model=schemas.User)
def read_users_me(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
    user = get_user(db, token=token)
    if not user:
        raise HTTPException(status_code=401, detail="Invalid authentication credentials")
    return user

Frontend Authentication

Use the power of Axios interceptors to add the JWT token to each request automatically. No need to repeat yourself every time you send a request.

import axios from 'axios';

axios.interceptors.push({
  request: config => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
});

Deploying Like a Pro

Let’s get this beast running live. Use Docker to containerize both frontend and backend parts of your project. Think of Docker as packing your entire app into a neat and portable box.

Docker Compose Setup

Create a docker-compose.yml file to outline your services. This file defines how to piece together your app’s services using Docker:

version: '3'
services:
  db:
    image: postgres
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=dbname
    volumes:
      - db-data:/var/lib/postgresql/data

  backend:
    build: ./fastapi
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/dbname
    ports:
      - "8000:8000"
    depends_on:
      - db

  frontend:
    build: ./react
    ports:
      - "3000:3000"
    depends_on:
      - backend

volumes:
  db-data:

Run everything with a simple command. It’s like hitting the start button on your favorite game console:

docker-compose up

Head over to http://localhost:3000 to see your app in action.

Testing, Testing, 1-2-3

Ensuring your application behaves is a big deal. Use Pytest for backend and Jest for frontend tests to keep everything under control.

Backend

Write your tests to make sure everything is functioning behind the scenes:

from fastapi.testclient import TestClient
from .main import app

client = TestClient(app)

def test_create_user():
    response = client.post("/users/", json={"email": "[email protected]", "password": "password"})
    assert response.status_code == 201
    assert response.json()["email"] == "[email protected]"

Frontend

For React components, use Jest to ensure interactions happen just the way you intend:

import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import axios from 'axios';
import Login from './Login';

jest.mock('axios');

describe('Login', () => {
  it('should call the login API', async () => {
    axios.post.mockResolvedValue({ data: { access_token: 'token' } });
    const { getByPlaceholderText, getByText } = render(<Login />);
    const emailInput = getByPlaceholderText('Email');
    const passwordInput = getByPlaceholderText('Password');
    const submitButton = getByText('Login');

    fireEvent.change(emailInput, { target: { value: '[email protected]' } });
    fireEvent.change(passwordInput, { target: { value: 'password' } });
    fireEvent.click(submitButton);

    await waitFor(() => expect(axios.post).toHaveBeenCalledTimes(1));
    expect(axios.post).toHaveBeenCalledWith('http://localhost:8000/token', expect.anything());
  });
});

Wrapping Things Up

Building a full-stack application with FastAPI, React, and PostgreSQL is like assembling the coolest LEGO set ever. Each step, from setting up project structure to deploying with Docker, adds a piece to your masterpiece. Following this guide, you’ll end up with a robust, scalable, and maintainable web application that’s ready to take on the world.

Just remember, the journey is just as important as the destination. Happy coding!