Production-ready URL shortener backend built with Fastify, TypeScript, PostgreSQL, Redis, Prisma, and BullMQ.
This project was designed as a backend engineering portfolio project demonstrating scalable architecture patterns used in real-world distributed systems.
Flashlink Service provides a complete short-link platform with:
- short link creation
- redirect resolution
- Redis cache-aside strategy for hot lookups
- asynchronous click analytics processing with a worker
- PostgreSQL persistence with Prisma
- configurable rate limiting
- optional link expiration
- Dockerized local development and deployment
- GitHub Actions CI pipeline
- Railway deployment
- OpenAPI documentation with Swagger UI
This service is deployed on Railway and exposes public documentation and health monitoring endpoints.
This implementation follows the architecture described in the system design repository:
https://github.com/marcos-astudillo/system-design-notes
The goal is to demonstrate how a system design document can be translated into a production-style backend implementation.
At a high level, the system works like this:
- The client creates a short URL through the API.
- The API stores link metadata in PostgreSQL.
- Redirect requests first try Redis.
- On cache miss, the API loads the link from PostgreSQL and populates Redis.
- Redirects are returned immediately.
- Click analytics are published asynchronously to a BullMQ queue.
- A worker consumes analytics jobs and updates daily aggregates in PostgreSQL.
Client
|
v
Fastify API
|-----------------------> PostgreSQL (links, daily click aggregates)
|
|-----------------------> Redis (redirect cache, queue backend)
|
'-- publish analytics --> BullMQ queue --> Worker --> PostgreSQL
- Runtime: Node.js
- Language: TypeScript
- Framework: Fastify
- ORM: Prisma
- Database: PostgreSQL
- Cache / Queue Backend: Redis
- Background Jobs: BullMQ
- Testing: Vitest + Supertest
- Containerization: Docker + Docker Compose
- CI: GitHub Actions
- Deployment: Railway
flashlink-service/
├── src/
│ ├── common/
│ ├── health/
│ ├── infrastructure/
│ ├── modules/
│ │ ├── analytics/
│ │ ├── links/
│ │ └── redirect/
│ ├── app.ts
│ ├── server.ts
│ └── worker.ts
├── prisma/
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
├── docker/
│ ├── api/
│ └── worker/
├── docs/
│ └── images/
├── .github/workflows/
├── docker-compose.yml
├── package.json
└── README.md
Creates a short code for a provided URL.
Endpoint
POST /v1/linksRequest body
{
"longUrl": "https://www.google.com",
"expiresAt": "2026-12-31T00:00:00.000Z"
}Response
{
"code": "Ab12xYz",
"longUrl": "https://www.google.com",
"shortUrl": "https://your-domain/Ab12xYz",
"createdAt": "2026-03-10T00:00:00.000Z",
"expiresAt": "2026-12-31T00:00:00.000Z"
}Resolves a short code and returns an HTTP redirect.
Endpoint
GET /:codeBehaviors
302 Foundif the short link exists404 Not Foundif the code does not exist410 Goneif the link is expired
Redirect resolution first checks Redis for fast reads. On cache miss, the API loads from PostgreSQL and stores the result in Redis.
Redirect requests do not block on analytics writes.
- API publishes click events to BullMQ
- Worker consumes jobs asynchronously
- PostgreSQL stores daily click aggregates in
ClickEventDaily
The service exposes a health endpoint with infrastructure dependency status.
Endpoint
GET /healthResponse
{
"status": "ok",
"service": "flashlink-service",
"checks": {
"database": "up",
"redis": "up"
}
}- feature flags
- route-level configurable rate limiting
- graceful shutdown for API and worker
- Dockerized services
- CI workflow for typecheck and tests
- OpenAPI / Swagger docs
Swagger UI is available at:
/docs
OpenAPI spec is available at:
/openapi.json
npm installCreate a .env file based on .env.example.
Example:
NODE_ENV=development
PORT=3000
BASE_URL=http://127.0.0.1:3000
LOG_LEVEL=info
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/flashlink
REDIS_URL=redis://localhost:6379
FEATURE_ANALYTICS=true
FEATURE_LINK_EXPIRATION=true
FEATURE_DEDUPE=false
RATE_LIMIT_ENABLED=true
REDIRECT_CACHE_TTL_SECONDS=3600
RATE_LIMIT_MAX=100
RATE_LIMIT_WINDOW_MINUTES=1
CREATE_LINK_RATE_LIMIT_MAX=20
REDIRECT_RATE_LIMIT_MAX=300docker compose up -d postgres redisnpx prisma migrate devnpm run devnpm run worker:devhttp://127.0.0.1:3000/docs
docker compose up --buildThis starts:
- API
- Worker
- PostgreSQL
- Redis
docker compose down
npm run build
npm run typechecknpm run test:unitRequires PostgreSQL and Redis:
docker compose up -d postgres redis
npm run test:integrationRequires PostgreSQL, Redis, and the worker:
docker compose up -d postgres redis
npm run worker:dev
npm run test:e2enpm run test:runGitHub Actions runs:
- dependency installation
- Prisma client generation
- migrations
- typecheck
- unit tests
- integration tests
Workflow file:
.github/workflows/ci.yml
This project is designed to deploy cleanly on Railway using four services in the same project:
- API
- Worker
- PostgreSQL
- Redis
- public domain enabled
- Dockerfile path:
docker/api/Dockerfile - start command:
sh -c "npx prisma migrate deploy && node dist/server.js"- no public domain required
- Dockerfile path:
docker/worker/Dockerfile - start command:
sh -c "npx prisma migrate deploy && node dist/worker.js"This project intentionally includes several production-oriented backend patterns:
- Cache-aside redirect reads reduce database pressure on hot links.
- Async analytics processing keeps redirect latency low.
- Worker separation isolates background processing from the request path.
- Feature flags make behavior configurable without code changes.
- Graceful shutdown improves deploy safety and service reliability.
- Dockerized services provide consistent local and cloud execution.
- Typed schemas and CI improve maintainability and correctness.
This project is licensed under the MIT License.
See the LICENSE file for details.





