A secure URL shortener: users authenticate to create and list short links; anyone can open a short URL and receive an HTTP 302 redirect to the original destination. Built as a full-stack reference implementation with Docker-first workflows.
| Aspect | Description |
|---|---|
| Access control | Sign up / sign in (JWT). Creating and listing links requires a valid bearer token. |
| Public behaviour | GET /{slug} resolves a stored link and redirects with 302 to the target URL. |
| Frontend | Root / is the auth experience; /dashboard manages links. |
| Operations | PostgreSQL, Prisma migrations, and services are orchestrated with Docker Compose. |
| Layer | Technology |
|---|---|
| API | NestJS 11, TypeScript |
| ORM / DB access | Prisma ORM 7, @prisma/adapter-pg + pg |
| Database | PostgreSQL 15 |
| Auth | JWT (@nestjs/jwt), Passport JWT strategy, bcrypt password hashing |
| Validation | class-validator, class-transformer |
| Frontend | Next.js (App Router), React 19, Tailwind CSS 4, Axios |
| Deployment | Docker, Docker Compose |
- User auth — Registration and login; passwords hashed with bcrypt; API access via JWT bearer tokens.
- Dashboard — Create short links and view your list (full short URLs shown for copy/open).
- Slug generation — 6-character alphanumeric codes; uniqueness enforced in the database with collision retry.
- Public redirect — Unauthenticated
GET /:slug→ 302 to the stored original URL; clear 404 message when the slug does not exist. - Dockerized stack — API, web UI, and database run from a single Compose file; backend waits for DB health before starting.
- Docker and Docker Compose (v2 plugin:
docker compose, or classicdocker-compose)
From the repository root:
docker-compose up --build| Service | URL / port |
|---|---|
| Frontend | http://localhost:3001 |
| Backend API | http://localhost:3000 |
| PostgreSQL | localhost:5432 (credentials in docker-compose.yml) |
The backend service depends on the database healthcheck and, on startup, runs npx prisma migrate deploy before npm run start:dev, so a fresh clone usually comes up with schema applied automatically.
If you need to run migrations without starting the full API (e.g. empty DB, CI, or troubleshooting):
docker-compose run --rm backend npx prisma migrate deployUse the same command after pulling migration changes. Ensure the db service is reachable (e.g. docker-compose up -d db first, or run while the stack is up).
| Variable | Where | Purpose |
|---|---|---|
DATABASE_URL |
Backend | PostgreSQL connection string (Compose sets this to host db) |
JWT_SECRET |
Backend | Symmetric secret for signing JWTs |
PORT |
Backend | HTTP port (default 3000) |
NEXT_PUBLIC_API_URL |
Frontend | Browser-visible API base URL (e.g. http://localhost:3000) |
End-to-end tests exercise: register → login → create URL → list URLs → public redirect (302).
With Compose (database must be available to the backend container):
docker-compose run --rm backend npm run test:e2eThe test:e2e script runs prisma migrate deploy then Jest. For local runs without Docker, set DATABASE_URL and run npm run test:e2e from the backend/ directory.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/auth/register |
No | Create account; returns { access_token } |
POST |
/auth/login |
No | Obtain { access_token } |
POST |
/urls |
Bearer JWT | Create a short link |
GET |
/urls |
Bearer JWT | List current user’s links |
GET |
/:slug |
No | Public redirect (302) to original URL |
-
Prisma 7 and the PostgreSQL driver adapter
Prisma 7 expects a driver adapter for direct TCP access. This project uses@prisma/adapter-pgwith thepgdriver so the client talks to PostgreSQL efficiently while keeping migrations and schema in Prisma. -
JWT + Passport strategy
Authentication is stateless: after login or register, the client stores the JWT and sendsAuthorization: Bearer <token>on protected routes. The server validates the token with a Passport JWT strategy and attaches the user context for URL ownership checks. -
Route layout: no global
/apiprefix and root-level redirects
JSON endpoints live at/authand/urlson the same origin as the redirect handler. There is noapp.setGlobalPrefix('api'), so public short links can stay asGET /{slug}at the root. Reserved slugs (e.g.auth,urls,health) are excluded at generation time so random codes do not shadow real routes. -
Validation at the edge
DTOs withclass-validatorkeep request bodies predictable and return consistent validation errors before business logic runs. -
CORS
The API enables CORS for browser calls from the Next.js app when origins differ during development.
.
├── backend/ # NestJS API, Prisma schema & migrations
├── frontend/ # Next.js App Router UI
├── docker-compose.yml # db, backend, frontend
└── README.md
Muhammad Ahmad Tariq
For questions or review feedback related to this technical test repository, reach out via the channels you use with the maintainer.