A simple URL shortener backend.
- POST /api/v1/urls/shorten - submit a long URL, get back a short code + short URL
- GET /{short_code} - redirects to the original long URL
- GET /api/v1/urls/{short_code} - view metadata about a short URL (no redirect)
- GET /health - health check
- GET /docs - interactive Swagger UI (auto-generated by FastAPI)
url-shortener/
├── app/
│ ├── main.py # App creation, middleware, router wiring, DB lifecycle
│ ├── core/
│ │ ├── config.py # Settings loaded from .env
│ │ └── database.py # MongoDB (Motor) connection management
│ ├── models/
│ │ └── url.py # DB document shape
│ ├── schemas/
│ │ └── url.py # Request/response (Pydantic) schemas
│ ├── routers/
│ │ ├── url.py # /api/v1/urls/* routes
│ │ └── redirect.py # /{short_code} redirect route
│ ├── services/
│ │ └── url_service.py # Business logic
│ ├── middleware/
│ │ └── logging.py # Request logging middleware
│ └── utils/
│ └── shortcode.py # Short code generator
├── .env # Template - copy to .env
├── docker-compose.yml
├── Dockerfile
└── pyproject.toml
This project ships only an .env.example (safe to commit). You must copy it
to a real .env file, which is git-ignored and holds your actual secrets.
cp .env.example .envYou generally do not need to change anything inside .env to run this
locally with Docker Compose - the defaults already match each other
(the Mongo username/password in MONGO_URI match MONGO_ROOT_USER /
MONGO_ROOT_PASSWORD). But you should still open .env and understand it:
| Variable | What it means | When to change it |
|---|---|---|
BASE_URL |
Used to build the short URL returned to clients, e.g. http://localhost:8000/aZ3kD9 |
Change to your real domain when deploying (e.g. https://short.mydomain.com) |
SHORT_CODE_LENGTH |
How many characters in each short code | Increase (e.g. to 8) if you expect millions of URLs |
MONGO_URI |
Full MongoDB connection string | Change if you use MongoDB Atlas (cloud) or a different local setup - see below |
MONGO_DB_NAME |
Database name inside MongoDB | Usually fine as-is |
MONGO_ROOT_USER / MONGO_ROOT_PASSWORD |
Credentials docker-compose uses to create the MongoDB root user | Change these to your own password before deploying anywhere public. If you change them, you must also update MONGO_URI to match. |
ALLOWED_ORIGINS |
CORS - which frontend origins can call this API | Change from * to your actual frontend URL (e.g. http://localhost:3000) once you build a frontend |
Important: if you change MONGO_ROOT_USER or MONGO_ROOT_PASSWORD, you
must update the username/password portion of MONGO_URI to match, e.g.:
MONGO_ROOT_USER=my_new_user
MONGO_ROOT_PASSWORD=my_new_pass
MONGO_URI=mongodb://my_new_user:my_new_pass@mongodb:27017
You have 3 options - pick ONE:
Option A - Docker Compose (recommended, easiest): MongoDB runs
automatically as a container. Keep MONGO_URI as:
MONGO_URI=mongodb://mongo_user:mongo_pass@mongodb:27017
(mongodb here is the docker-compose service name - not a typo, and not
localhost, since the app talks to Mongo over the Docker network.)
Option B - MongoDB installed locally on your machine (no Docker for Mongo):
Change MONGO_URI in .env to:
MONGO_URI=mongodb://localhost:27017
(No username/password needed if you haven't set up auth locally.)
Option C - MongoDB Atlas (free cloud database):
- Create a free cluster at https://www.mongodb.com/cloud/atlas
- Get the connection string Atlas gives you (looks like
mongodb+srv://user:pass@cluster0.xxxxx.mongodb.net) - Paste it into
.envasMONGO_URI - In Atlas, whitelist your IP address (Network Access tab) or allow
access from anywhere (
0.0.0.0/0) for local testing.
This starts both the API and MongoDB together.
docker compose up --buildThen visit:
- http://localhost:8000/docs (Swagger UI)
- http://localhost:8000/health
To stop:
docker compose downTo stop AND delete the MongoDB data volume (fresh start):
docker compose down -vYou still need MongoDB running somewhere (see Option B or C above).
-
Install
uvif you don't have it:curl -LsSf https://astral.sh/uv/install.sh | sh(Windows: see https://docs.astral.sh/uv/getting-started/installation/)
-
Install dependencies:
uv sync
-
Make sure your
.envfile exists (step 1 above) andMONGO_URIpoints to a MongoDB instance you can actually reach (e.g.mongodb://localhost:27017if running Mongo locally, NOTmongodb://mongodb:27017- that hostname only resolves inside docker-compose's network). -
Run the app:
uv run uvicorn app.main:app --reload
curl -X POST http://localhost:8000/api/v1/urls/shorten \
-H "Content-Type: application/json" \
-d '{"original_url": "https://www.google.com/search?q=fastapi"}'Response:
{
"original_url": "https://www.google.com/search?q=fastapi",
"short_code": "aZ3kD9",
"short_url": "http://localhost:8000/aZ3kD9",
"created_at": "2026-07-22T10:00:00Z"
}Open in a browser, or:
curl -iL http://localhost:8000/aZ3kD9curl http://localhost:8000/api/v1/urls/aZ3kD9motoris used as the async MongoDB driver so it plays nicely with FastAPI's async request handlers (no blocking calls).- Unique index on
short_codeis created automatically on startup, and the service layer retries a few times on the rare chance of a collision. secrets(notrandom) generates short codes, since it's cryptographically secure and non-guessable.- Redirect status 307 (Temporary Redirect) is used instead of 301 (Permanent) so that browsers/tools don't aggressively cache the redirect forever - useful if you ever want to change where a short code points.
Go to https://www.mongodb.com/cloud/atlas/register. Sign up (Google/email). Free, no card required for free tier.
- Click Create / Build a Database
- Select M0 Free tier
- Provider: AWS/GCP/Azure — any, doesn't matter for this use case
- Region: pick closest to where FastAPI Cloud runs your app (any is fine, latency difference is small)
- Cluster name: default
Cluster0works, or rename - Click Create Deployment
Prompted automatically after cluster creation, or go to Database Access (left sidebar):
- Click Add New Database User
- Authentication Method: Password
- Username: pick one, e.g.
url_shortener_app - Password: click Autogenerate Secure Password and copy it now — shown once
- Database User Privileges: Read and write to any database
- Click Add User
Go to Network Access (left sidebar):
- Click Add IP Address
- Click Allow Access From Anywhere (fills in
0.0.0.0/0) - Click Confirm
Required because FastAPI Cloud egress IPs aren't fixed/listable. Auth (step 3) still gates access — this only controls which IPs can attempt a connection.
- Go to Database (left sidebar) → click Connect on your cluster
- Choose Drivers
- Driver: Python, Version: 3.12 or later
- Copy the string shown, format:
mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
- Replace
<username>and<password>with values from step 3 - If password has special characters (
@,:,/,%), URL-encode them (e.g.@→%40)
Connection string above doesn't specify a database name. Add it after the host, before the ?:
mongodb+srv://url_shortener_app:yourpassword@cluster0.xxxxx.mongodb.net/url_shortener?retryWrites=true&w=majority&appName=Cluster0
This matches MONGO_DB_NAME=url_shortener from your .env.example. Database itself doesn't need pre-creation — MongoDB creates it on first write.
cd url-shortener
cp .env.example .envEdit .env, set:
MONGO_URI=mongodb+srv://url_shortener_app:yourpassword@cluster0.xxxxx.mongodb.net/url_shortener?retryWrites=true&w=majority&appName=Cluster0
Run:
uv run fastapi devCheck terminal for Successfully connected to MongoDB log line (from app/core/database.py). If connection fails, error will show at startup — check password encoding and Network Access setting.
uv run fastapi cloud env set --secret MONGO_URI "mongodb+srv://url_shortener_app:yourpassword@cluster0.xxxxx.mongodb.net/url_shortener?retryWrites=true&w=majority&appName=Cluster0"--secret flag encrypts it, hides value in dashboard after creation.
Also set:
uv run fastapi cloud env set MONGO_DB_NAME "url_shortener"(Redundant with database name in URI, but your config.py reads MONGO_DB_NAME separately — keep both in sync.)
rm .envAlready gitignored, but don't paste real credentials into any file you might commit.
FastAPI Cloud (https://fastapicloud.com) is a managed hosting platform built
by the FastAPI team. This project is already set up to work with it -
fastapi[standard] is in the dependencies (this bundles the FastAPI Cloud
CLI) and app/main.py exposes an app object, which is one of the
standard locations FastAPI Cloud auto-detects.
Sign up at https://fastapicloud.com if you haven't already. At the time of writing, new accounts may go through a short waitlist - the CLI will prompt you with a "Join the waiting list" option if so.
FastAPI Cloud hosts your FastAPI application process only - it does not
run a MongoDB container alongside it the way docker-compose.yml does
locally. You need a MongoDB instance reachable over the public internet.
The easiest option is a free MongoDB Atlas cluster:
- Create a free cluster at https://www.mongodb.com/cloud/atlas
- Under Network Access, add
0.0.0.0/0("allow access from anywhere"). This is necessary because FastAPI Cloud's outbound IP addresses aren't fixed/predictable, so you can't allowlist a specific IP. (Your database still requires the username/password in the connection string - this setting only controls which IPs are allowed to attempt a connection.) - Under Database Access, create a database user and password.
- Copy the connection string Atlas gives you - it looks like:
mongodb+srv://<user>:<password>@cluster0.xxxxx.mongodb.net
From the project root:
uv run fastapi loginThis opens your browser to authenticate.
uv run fastapi deployThe CLI will package your code (respecting .gitignore and
.fastapicloudignore), upload it, and build + deploy it. You'll get a URL
like:
https://url-shortener.fastapicloud.dev
Your local .env file is never uploaded (it's git-ignored), so you need to
set the same variables in FastAPI Cloud. Do this via the CLI:
Manage secret/non-secret environment variables through the dashboard at
https://dashboard.fastapicloud.com
Click Your app (url-shortener-api) > Environment Variables tab > Import .env
After setting/changing environment variables, redeploy so the app picks them up:
uv run fastapi deployhttps://url-shortener.fastapicloud.dev/health
https://url-shortener.fastapicloud.dev/docs
Check real-time logs any time from the dashboard: Apps → your app → Logs.
If you're using the Threadbox frontend from this same set of deliverables,
update its js/config.js:
const CONFIG = {
API_BASE_URL: "https://url-shortener.fastapicloud.dev",
};And make sure ALLOWED_ORIGINS (step 4 above) matches wherever you deploy
that frontend - otherwise the browser will block the requests with a CORS
error even though the API itself works fine.
- You don't need
Dockerfileordocker-compose.ymlfor FastAPI Cloud - they're excluded via.fastapicloudignoreand only needed for local Docker-based development. Keep using them locally if you like; both paths work off the same codebase. fastapi deploycan be re-run any time you push changes - each run redeploys the current code.- For automatic deploys on every git push, see FastAPI Cloud's GitHub integration docs: https://fastapicloud.com/docs/source-control/github-integration