Building a full-stack CRUD application with user authentication and file handling involves developing both frontend and backend components that work together to manage data securely and efficiently.
The backend provides APIs for creating, reading, updating, and deleting records, handles user authentication and authorization, and manages file uploads and storage. The frontend interacts with these APIs to present data, manage user sessions, and allow users to upload and access files through an intuitive interface.
Project Overview and Tech Stack
This section outlines the app we'll build: a file-sharing dashboard where authenticated users can create, view, edit, and delete notes, each attached to an uploaded image file. It ties together API design, security, and frontend integration, reflecting industry standards like those from OWASP for auth and REST API guidelines.
We'll use a modern, efficient stack that's beginner-friendly yet scalable.
1. Backend: FastAPI (Python) for APIs, SQLAlchemy for ORM, JWT for auth, and Alembic for migrations.
2. Database: PostgreSQL for relational data with file metadata.
3. Frontend: React with Axios for API calls, React Router for navigation, and Tailwind CSS for styling.
4. File Handling: Secure uploads to a local directory (extendable to AWS S3).
5. Other Tools: Docker for containerization, environment variables for secrets.
This stack ensures your app handles 100+ concurrent users efficiently, per benchmarks from FastAPI docs.
Setting Up the Backend with FastAPI
Start by creating a robust backend that exposes CRUD endpoints secured by authentication. FastAPI's dependency injection makes auth seamless, while Pydantic models enforce data validation.
First, install dependencies: pip install fastapi uvicorn sqlalchemy alembic python-jose[cryptography] python-multipart passlib[bcrypt] psycopg2-binary.
Database Models and Migrations
Define your models for users, notes, and files. Use SQLAlchemy for ORM to map Python classes to tables.
# models.py
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
Base = declarative_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)
class Note(Base):
__tablename__ = "notes"
id = Column(Integer, primary_key=True, index=True)
title = Column(String)
content = Column(String)
file_path = Column(String) # Stores relative path to uploaded file
owner_id = Column(Integer, ForeignKey("users.id"))
created_at = Column(DateTime, default=datetime.utcnow)Run Alembic migrations: alembic init migrations, configure alembic.ini, then alembic revision --autogenerate -m "initial" and alembic upgrade head.
Implementing User Authentication
Secure your API with JWT tokens. FastAPI's OAuth2PasswordBearer handles token extraction effortlessly.
1. Create a password hasher utility using passlib.
2. Define auth schemas in Pydantic.
3. Implement /auth/register and /auth/login endpoints.
Example login endpoint
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from datetime import timedelta
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login")
SECRET_KEY = "your-secret-key" # Use env vars in prod
ALGORITHM = "HS256"
@app.post("/auth/login")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = get_user_by_email(form_data.username) # Implement this
if not verify_password(form_data.password, user.hashed_password):
raise HTTPException(status_code=400, detail="Incorrect credentials")
access_token = jwt.encode({"sub": user.email}, SECRET_KEY, algorithm=ALGORITHM)
return {"access_token": access_token, "token_type": "bearer"}Protect routes with a dependency: @app.get("/notes/", dependencies=[Depends(get_current_user)]).
CRUD Operations for Notes
With auth in place, build CRUD endpoints for notes. Each note links to a user and stores file metadata—ensuring atomic operations via database transactions.
Create and Read Endpoints
POST /notes/: Create a note (requires auth).
1. Validate input with Pydantic.
2. Associate with current_user.id.
Example
@app.post("/notes/")
async def create_note(note: NoteCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
db_note = Note(**note.dict(), owner_id=current_user.id)
db.add(db_note)
db.commit()
db.refresh(db_note)
return db_noteGET /notes/: List user's notes (paginated for scalability).
1. Filter by owner_id.
2. Use limit and offset query params.
Bullet key features: Pagination prevents overload; SQLAlchemy's order_by ensures latest-first sorting.
Update and Delete Endpoints
PUT /notes/{note_id} and DELETE /notes/{note_id} follow similar patterns, with ownership checks.
@app.delete("/notes/{note_id}")
async def delete_note(note_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
note = db.query(Note).filter(Note.id == note_id, Note.owner_id == current_user.id).first()
if not note:
raise HTTPException(status_code=404, detail="Note not found")
db.delete(note)
db.commit()
return {"msg": "Note deleted"}
These align with REST conventions (RFC 7231), ensuring intuitive API consumption.
Secure File Upload and Handling
File handling introduces security risks like arbitrary code execution—mitigate with validation and isolation. FastAPI's UploadFile makes this straightforward.
Upload Endpoint Integration
Extend the create/update flow:
1. In POST /notes/, accept file: UploadFile.
2. Validate: Check MIME type (e.g., images only), size (<5MB).
3. Save to ./uploads/{user_id}/{filename} with unique names (UUID).
import shutil
from fastapi import File, UploadFile
import os
from uuid import uuid4
UPLOAD_DIR = "uploads"
@app.post("/notes/")
async def create_note_with_file(
title: str, content: str, file: UploadFile = File(...),
current_user: User = Depends(get_current_user), db: Session = Depends(get_db)
):
if not file.content_type.startswith("image/"):
raise HTTPException(400, "Only images allowed")
filename = f"{uuid4()}.{file.filename.split('.')[-1]}"
os.makedirs(f"{UPLOAD_DIR}/{current_user.id}", exist_ok=True)
path = f"{UPLOAD_DIR}/{current_user.id}/{filename}"
with open(path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
note = Note(title=title, content=content, file_path=path, owner_id=current_user.id)
db.add(note)
db.commit()
return note1. Serve files statically: Use FastAPI's StaticFiles mount at /uploads.
2. Best practices: Scan with ClamAV in prod; delete files on note deletion to free storage.
For downloads: GET /notes/{id}/file streams the file securely.
Building the React Frontend
Shift to the frontend: A responsive dashboard with login, note list, forms, and file previews. Use React Query (TanStack Query) for optimistic updates and caching.
Setup and Key Components
npx create-react-app frontend --template typescript, then npm i axios react-router-dom @tanstack/react-query tailwindcss.
Core components
1. AuthContext: Manages JWT token in localStorage.
2. NoteList: Fetches and displays notes with image previews.
3. NoteForm: Handles create/edit with file input.
Example API hook
import { useMutation, useQuery } from '@tanstack/react-query';
import axios from 'axios';
const api = axios.create({ baseURL: 'http://localhost:8000' });
export const useNotes = () => {
return useQuery(['notes'], () => api.get('/notes/', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
}).then(res => res.data));
};UI Flows: Login to CRUD
1. Login Page: Form submits to /auth/login, stores token.
2. Dashboard: Protected route shows note cards with file preview (<img src={/uploads/${note.file_path}} />).
3. Create/Edit Modal: Drag-and-drop file upload, form validation with React Hook Form.
4. Delete Confirmation: Optimistic UI update via React Query mutations.
Responsive design: Tailwind classes ensure mobile-first.
Error handling: Toast notifications for 401/404.
Deployment and Best Practices
Package for production: Dockerize backend (Dockerfile with multi-stage build), deploy frontend to Vercel/Netlify, backend to Render/Heroku. Use Cloudinary or S3 for files.
Key practices
This app scales to enterprise levels, as seen in FastAPI's production case studies (e.g., Uber).