Logo Relancio Borges
All projects

↘ Case study · 2026

URL Shortener

High-performance URL shortening API using Fastify, PostgreSQL, and Drizzle ORM

Project cover image for URL Shortener

A high-performance URL-shortening REST API built with Fastify, TypeScript, and PostgreSQL. The project follows Clean Architecture principles and emphasizes observability, security, and scalability.

URL Shortener API Interactive API documentation in Swagger.


Why I Built This Project

I enjoy building REST APIs and wanted to understand how services such as Bitly and TinyURL work behind the scenes. The project was designed to demonstrate:

  • Clean Architecture – Clear separation of responsibilities.
  • Observability – Metrics, structured logs, and distributed traces.
  • Performance – In-memory caching and optimized indexes.
  • Security – Input validation and rate limiting.
  • Containerization – A complete Docker Compose infrastructure.

I selected Fastify because of its low overhead, native TypeScript support, and strong performance. PostgreSQL and Drizzle ORM provide reliable relational persistence without the weight of a larger ORM.


Project Architecture

url-shortcut/
├── src/
│   ├── core/                      # Shared entities, types, and Either
│   ├── domain/urls/
│   │   ├── application/           # Repository contracts and use cases
│   │   └── enterprise/entities/   # Domain models
│   ├── infra/
│   │   ├── cache/                 # In-memory cache
│   │   ├── database/              # Drizzle repositories and schemas
│   │   ├── env/                   # Environment validation
│   │   ├── http/                  # Controllers, presenters, and routes
│   │   ├── logging/               # Structured logging
│   │   ├── metrics/               # Prometheus metrics
│   │   └── utils/                 # Infrastructure utilities
│   └── types/                     # Global TypeScript definitions
├── config/                        # Grafana, Loki, Tempo, Traefik, and OTel
└── test/                          # Unit and end-to-end tests

Architectural Decisions

AreaDecisionReason
FrameworkFastify 5Low overhead and excellent performance
DatabasePostgreSQL + DrizzleACID guarantees, JSON support, and maturity
CacheIn-memory MapFive-minute TTL for frequently visited URLs
LogsStructured JSONDirect Loki and Grafana integration
ProxyTraefik 3Service discovery and Let’s Encrypt support
ObservabilityOpenTelemetryVendor-neutral industry standard

Technologies and Tools

  • Node.js 22, TypeScript 5, Fastify 5, and PostgreSQL 15+.
  • Drizzle ORM for lightweight, typed persistence.
  • Docker and Traefik 3 for deployment and reverse proxying.
  • Prometheus, Grafana, Loki, Tempo, and OpenTelemetry for observability.
  • Pino for structured JSON logs.
  • Zod for runtime schema validation and end-to-end typing.

Features

Core Features

  • Automatic short-code generation.
  • Redirects with click, IP, user-agent, and geolocation tracking.
  • Click analytics for each link.
  • Optional expiration dates.
  • Configurable rate limits.

Performance

  • Five-minute in-memory cache for frequently accessed URLs.
  • Optimized indexes on short_code and created_at.
  • Non-blocking analytics tracking.

Security

  • Blocks dangerous protocols such as javascript: and data:.
  • Prevents redirects to private network addresses.
  • End-to-end validation with Zod and TypeScript.

Observability

  • Prometheus metrics for latency, cache hits, and clicks.
  • Correlated structured request logs.
  • Liveness, readiness, and complete health checks.
  • Distributed tracing with OpenTelemetry.

Technical Challenges

1. Redirect Performance

Redirects must be extremely fast. A five-minute in-memory TTL cache avoids unnecessary database queries for popular short codes.

const cachedUrl = cache.get(shortCode);
if (cachedUrl) {
  metrics.cacheHits.inc();
  return redirect(cachedUrl);
}

const url = await urlRepository.findByShortCode(shortCode);
if (url) cache.set(shortCode, url.originalUrl, TTL);
metrics.cacheMisses.inc();

2. Asynchronous Analytics

Click tracking must not increase redirect latency. The response is returned immediately while analytics is persisted asynchronously in the background.

redirectAndTrackUrlUseCase.execute(shortCode, request)
  .then((url) => reply.redirect(url.originalUrl))
  .catch(() => {
    // An analytics failure must not break the redirect.
  });

3. Complete Observability

The production stack combines custom Prometheus metrics, request-correlated JSON logs, OpenTelemetry traces, and ready-to-use Grafana dashboards.

const httpDuration = new Histogram({
  name: "http_request_duration_seconds",
  help: "Duration of HTTP requests",
  labelNames: ["method", "route", "status_code"],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1],
});

4. URL Validation

Zod validation blocks dangerous protocols and private IP addresses before a link is stored.

const urlSchema = z.string()
  .url()
  .refine((url) => !["javascript:", "data:", "vbscript:"].includes(url.protocol), {
    message: "Invalid protocol",
  })
  .refine((url) => !isPrivateIP(url), {
    message: "Private IPs not allowed",
  });

What I Learned

Technical Skills

  • Fastify plugins, hooks, and schema validation.
  • Drizzle queries, migrations, and schema design.
  • Clean Architecture and dependency injection.
  • Docker Compose and a complete observability stack.
  • Rate-limiting and caching strategies.

Engineering Skills

  • REST design and correct status-code selection.
  • Swagger and OpenAPI documentation.
  • Database indexing and latency optimization.
  • Debugging through structured logs and traces.

API Endpoints

# Create a short URL
POST /api/links
{ "url": "https://example.com/very/long/url" }

# Redirect
GET /{shortCode}

# Manage links and analytics
GET    /api/links
DELETE /api/links/{shortCode}
GET    /api/analytics/{shortCode}

# Health and metrics
GET /health
GET /health/live
GET /health/ready
GET /metrics

Available Scripts

# Development and build
npm run dev
npm run build
npm run start

# Tests
npm test
npm run test:unit
npm run test:e2e
npm run test:watch

# Database
npx drizzle-kit push
npx drizzle-kit generate

# Infrastructure
docker-compose up -d
docker-compose logs -f

Next Steps

  • JWT authentication for link ownership.
  • Web dashboard for managing URLs.
  • QR-code generation.
  • Custom short codes.
  • Visual analytics dashboard.
  • Redis-backed distributed caching.

Why This Project Matters

URL Shortcut demonstrates high-performance API design, clean and testable boundaries, end-to-end observability, Docker-based infrastructure, and security practices such as strict validation and rate limiting. It is a practical exploration of building robust, observable Node.js services rather than only a basic URL shortener.