A full-stack MVP URL shortener built with React, FastAPI, SQLAlchemy, and PostgreSQL.
Users can paste a long URL, generate a short link, copy it, and use the short link to redirect back to the original URL.
- Shorten long URLs
- Validate submitted URLs
- Generate collision-safe short codes
- Store links in PostgreSQL
- Redirect short links to original URLs
- Return
404for invalid short codes - Clean React UI with loading, error, result, and copy states
- FastAPI interactive API docs
- Basic backend tests with pytest
- React
- Vite
- JavaScript
- CSS
- FastAPI
- SQLAlchemy 2.0
- Alembic
- pytest
- PostgreSQL
- Neon
- Frontend: Vercel
- Backend: Render
- Database: Neon
url shortener/
├── backend/
│ ├── alembic/
│ ├── app/
│ │ ├── api/
│ │ ├── models/
│ │ ├── schemas/
│ │ ├── services/
│ │ ├── config.py
│ │ ├── database.py
│ │ └── main.py
│ ├── tests/
│ ├── alembic.ini
│ ├── requirements.txt
│ └── .env.example
├── frontend/
│ ├── src/
│ │ ├── App.jsx
│ │ ├── main.jsx
│ │ └── styles.css
│ ├── index.html
│ └── package.json
├── PROJECT_PLAN.md
├── README.md
└── .gitignoregit clone https://github.com/YOUR_USERNAME/url-shortener.git
cd url-shortenercd backend
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txtCreate a .env file:
Copy-Item .env.example .envAdd your Neon PostgreSQL connection string:
DATABASE_URL=postgresql+psycopg://USER:PASSWORD@HOST/DATABASE?sslmode=require
CORS_ORIGINS=http://localhost:5173Run database migrations:
alembic upgrade headStart the backend:
python -m uvicorn app.main:app --reloadBackend runs at:
http://127.0.0.1:8000API docs:
http://127.0.0.1:8000/docsOpen a second terminal:
cd frontend
npm install
npm run devFrontend runs at:
http://localhost:5173GET /healthResponse:
{
"status": "healthy"
}GET /health/databaseResponse:
{
"status": "healthy",
"database": "connected"
}POST /api/v1/linksRequest:
{
"original_url": "https://example.com"
}Response:
{
"id": "482686dd-3563-4c81-8527-42218c00111b",
"original_url": "https://example.com/",
"short_code": "ZwO2W7OB",
"short_url": "http://127.0.0.1:8000/ZwO2W7OB",
"created_at": "2026-07-09T09:17:34.755547Z"
}GET /{short_code}Example:
http://127.0.0.1:8000/ZwO2W7OBRedirects to the original URL.
From the backend directory:
.\.venv\Scripts\python.exe -m pytest tests/test_health.py tests/test_link_crud.py -p no:cacheproviderExpected result:
6 passedRender settings:
Root Directory: backend
Build Command: pip install -r requirements.txt
Start Command: alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port $PORTEnvironment variables:
DATABASE_URL=your_neon_database_url
CORS_ORIGINS=https://your-frontend.vercel.appVercel settings:
Root Directory: frontend
Build Command: npm run build
Output Directory: distEnvironment variable:
VITE_API_BASE_URL=https://your-render-backend.onrender.comThis version intentionally focuses only on the core MVP:
- no authentication
- no analytics
- no QR codes
- no Redis cache
- no custom aliases
- no expiration dates
- no Docker setup yet
Those can be added later as separate production features.
MIT