A full-stack user management application with a FastAPI backend and React + Vite frontend. It now presents the system as a professional dashboard with JWT authentication, protected CRUD operations, and backend-enforced role-based visibility.
| Layer | Technology |
|---|---|
| Backend | Python 3.11+, FastAPI, SQLAlchemy |
| Database | PostgreSQL (Alembic migrations) |
| Auth | Custom HS256 JWT + PBKDF2-SHA256 (Python stdlib only) |
| Authorization | Backend-enforced admin and user roles |
| Frontend | React 18, Vite 5 |
| Tests | Python unittest with SQLite |
User_Management/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app factory, lifespan, root route
│ ├── database.py # SQLAlchemy engine, session, Base
│ ├── models.py # User ORM model
│ ├── schemas.py # Pydantic request/response schemas
│ ├── auth.py # Password hashing + JWT (stdlib only)
│ ├── dependencies.py # Shared FastAPI deps: get_db, get_current_user
│ └── routers/
│ ├── auth.py # POST /auth/login GET /auth/me
│ └── users.py # CRUD /users
├── alembic/
│ ├── env.py
│ └── versions/
├── tests/
│ ├── __init__.py
│ └── test_app.py # unittest suite (runs against SQLite)
├── frontend/
│ ├── src/
│ │ ├── App.jsx # React SPA
│ │ ├── main.jsx
│ │ └── styles.css
│ ├── index.html
│ ├── vite.config.js
│ └── package.json
├── .env # secrets (git-ignored)
├── .env.example # template to copy from
├── alembic.ini
└── pyproject.toml # dependencies + ruff/pytest config
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/ |
No | Serves the React frontend |
POST |
/users |
No | Register a new user |
POST |
/auth/login |
No | Login — returns a JWT bearer token |
GET |
/auth/me |
Yes | Get the currently authenticated user |
GET |
/users |
Yes | List all users for any authenticated user |
GET |
/users/{id} |
Yes | Get a single user by ID if you are an admin or the account owner |
PUT |
/users/{id} |
Yes | Update a user if you are an admin or the account owner |
DELETE |
/users/{id} |
Yes | Delete a user if you are an admin or the account owner |
Protected endpoints require Authorization: Bearer <token>.
cp .env.example .envEdit .env:
DATABASE_URL=postgresql://user:password@localhost/user_management
JWT_SECRET_KEY=replace-with-a-long-random-secret
ACCESS_TOKEN_EXPIRE_MINUTES=60
DEFAULT_ADMIN_NAME=System Admin
DEFAULT_ADMIN_EMAIL=admin@user-management.local
DEFAULT_ADMIN_PASSWORD=Admin@12345Generate a secure secret:
python -c "import secrets; print(secrets.token_hex(32))"pip install -e ".[dev]"Or without dev tools:
pip install -e .sudo systemctl start postgresql
sudo -u postgres psql -c "CREATE USER your_user WITH PASSWORD 'your_password';"
sudo -u postgres psql -c "CREATE DATABASE user_management OWNER your_user;"alembic upgrade headuvicorn app.main:app --reload- API:
http://localhost:8000 - Swagger docs:
http://localhost:8000/docs
Production build (served by FastAPI at /):
cd frontend
npm install
npm run buildDevelopment (hot reload via Vite, API proxied to FastAPI):
cd frontend
npm run dev
# Open http://localhost:5173Tests use SQLite — no PostgreSQL setup needed:
python -m pytest tests/ -v
# or
python -m unittest discover -s tests -v- Register —
POST /userswithname,email,password. Password is hashed with PBKDF2-SHA256 (390,000 iterations) before storage. - Seed admin — On startup, the app creates a default
adminaccount only if the database has no users yet. - Assign role — Later registered accounts default to
user. - Login —
POST /auth/loginreturns a signed HS256 JWT withsub(user ID),email, andexpclaims. - Authorize — Send
Authorization: Bearer <token>on protected requests. - Verify —
get_current_userinapp/dependencies.pydecodes the token, validates the signature, checks expiry, and loads the user from the database.
adminusers can view and manage all accounts.useraccounts can view the directory and manage only their own record.- The frontend mirrors these backend permissions, but the backend remains the source of truth.
- Email:
admin@user-management.local - Password:
Admin@12345 - These can be changed with
DEFAULT_ADMIN_NAME,DEFAULT_ADMIN_EMAIL, andDEFAULT_ADMIN_PASSWORDin.env. - The seed runs only when the database is empty, so existing installations are left untouched.
- Open
http://localhost:8000/docs POST /usersto create an accountPOST /auth/loginwith the same credentials- Copy the
access_tokenfrom the response - Click Authorize → paste the token
- Call any protected endpoint
If you are studying the code:
app/database.py— engine and session setupapp/models.py— theuserstableapp/schemas.py— validation and response shapesapp/auth.py— PBKDF2 hashing and HS256 JWTapp/dependencies.py— shared FastAPI depsapp/routers/users.py— CRUD routesapp/routers/auth.py— login and current-user routesapp/main.py— app factory, router wiringfrontend/src/App.jsx— React UItests/test_app.py— test suite
app/auth.py uses only Python's standard library (hashlib, hmac, secrets) so every step of hashing and token signing is visible. The frontend shows controlled React forms, localStorage token storage, role-aware dashboard rendering, and fetch-based API calls. In production, teams typically use dedicated auth libraries rather than maintaining this logic by hand.