Skip to main content
George Khananaev
Technical Deep-Dive

Vercel Controller: a Node.js microservice for Vercel cache and deployments

Express 4 service with async job queue, smart deduplication, LRU cache, Helmet.js security, and 129 tests — called by Travel Panel whenever content changes

5 min read
Moon HolidaysJan 2026Head of Development & IT Infrastructure

Overview

Vercel Controller is a Node.js microservice (Express 4) that wraps the Vercel API behind a stable interface. It is called by the Travel Panel core platform whenever content changes: a new offer published, a hotel updated, an inventory adjustment, or a brand-level config change. The controller takes that signal, purges CloudFront cache for the affected paths, triggers a redeploy of the dependent Vercel frontends, and reports status back through an async job API.

It exists because operating a production front-end on Vercel at scale eventually means you need more than the Vercel dashboard. You need to trigger cache purges and deploys from CI, from webhooks, from operators, and from other services in the platform. You need to do it without tripping Vercel's rate limits or duplicating work. And you need to know whether the operation actually landed.

What it solves

Manual Vercel operations. Click, wait, click again. Bad for ops, invisible to automation.

Rate limit pain. Vercel imposes a 60-second interval on purge operations. Naive scripts that fire off multiple purges in a row fail silently or get throttled. The controller serializes operations behind a queue that respects the interval.

Duplicate jobs. If two systems trigger a cache purge for the same project within the same minute, you waste one call and risk inconsistency. Smart deduplication detects an in-flight job for the same project + operation key and attaches new requests to it instead of queuing a duplicate.

Visibility. Knowing whether an async operation finished and whether it succeeded requires polling, and polling requires a stable interface.

API endpoints

Method Endpoint Description
GET /health Health check with full stats (no auth)
GET /projects List all team projects (cached)
POST /purge/{project} Purge cache (async by default)
POST /purge-all Purge cache for all projects
POST /redeploy/{project} Redeploy a project (async by default)
GET /jobs List recent jobs
GET /jobs/{jobId} Get job status / result

Three purge types supported: all cache, data cache only, or CDN only. Redeploys target production or preview by default.

Health check output

The health endpoint returns everything an operator or monitoring system needs:

{
  "status": "ok",
  "service": "vercel-controller",
  "version": "1.3.5",
  "uptime": 3600,
  "memory": {"used": "45MB", "total": "128MB"},
  "config": {
    "team": "moon-holidays",
    "auth": "enabled",
    "swagger": "/docs",
    "rateLimit": "100 req/60s"
  },
  "cache": {
    "size": 5,
    "hits": 150,
    "misses": 20,
    "hitRate": "88%"
  },
  "queue": {
    "pending": 2,
    "processing": 1,
    "completed": 45,
    "failed": 0
  }
}

Security model

Every endpoint except /health and /docs requires Bearer token authentication. The token comparison is timing-safe to prevent side-channel leakage. Beyond auth:

  • Helmet.js 7 for security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
  • express-rate-limit 7 with configurable per-IP limits (default: 100 req/min)
  • CORS with explicit allowed-origin list
  • Strict input validation on every parameter (project ID, purge type, deploy target, IDs)
  • Request size limits of 10 KB max JSON payload to prevent oversize DoS
  • Startup validation: the service fails fast if VERCEL_TOKEN, VERCEL_TEAM_SLUG, or VERCEL_ORG_ID are missing — no running without credentials
  • Graceful shutdown on SIGTERM/SIGINT so in-flight requests complete and no queued jobs get stranded
  • Type confusion prevention in the validation layer

Testing

129 tests in total, organized by concern:

  • Validation functions (projectId, purge type, deploy target, secure compare)
  • API endpoints (health, auth, purge, redeploy, jobs, cache)
  • Queue service (enqueueing, deduplication, smart keys, stats)
  • Cache service (TTL expiration, LRU eviction, stats)
  • Job rate limiter (client isolation, window reset)
  • CORS wildcard support
  • Security (headers, error handling, type confusion prevention)

The jest config enforces 50% minimum coverage on branches, functions, lines, and statements as a floor, with the actual numbers higher across the critical modules.

Docker + auto-healing

Two docker-compose configurations:

Feature docker-compose.yml docker-compose.dev.yml
Swagger UI disabled /docs
Autoheal enabled disabled
Image size lighter larger (swagger-ui-express)
Restart policy always unless-stopped

Production includes autoheal: a sidecar monitors container health every 30 seconds and automatically restarts unhealthy containers. Zero-touch recovery for the common failure modes.

How Travel Panel uses it

When a Travel Panel operator publishes a new offer, updates a hotel listing, or adjusts inventory, the FastAPI backend makes an authenticated POST to Vercel Controller. Vercel Controller enqueues a purge + redeploy job for the affected frontend projects (TravelOffer, B2B portal, operator portal if its static bundle needs a refresh), deduplicates against any in-flight job, and returns a job ID immediately.

Travel Panel does not wait. The background job flushes CloudFront and triggers the Vercel deploys on its own cadence, respecting the 60-second purge interval and never doubling up work. Operators see a banner when a cache refresh is in progress and another when it lands.

This is the quietest piece of infrastructure in the whole Moon Holidays stack. You do not notice it unless it is missing.

Tech stack

Node.js, Express 4.18, Helmet.js 7, express-rate-limit 7, CORS, Swagger UI Express (dev only), in-memory LRU cache, in-memory job queue, Jest 29 with Supertest, Docker, Docker Compose with autoheal sidecar.

Version: 1.3.5. Port: 3500. License: Proprietary — Copyright © 2026 George Khananaev. All rights reserved.

Takeaway

Wrapping a third-party API behind a service you own pays off the moment you need to add deduplication, rate limiting, or observability. Vercel Controller started as a small script and grew into the right place to solve every Vercel-operations problem for the whole platform. Small, focused services like this are my favorite kind of infrastructure: they are easy to reason about, easy to replace, and they earn their keep every time someone skips a manual click.

For other small, focused utilities I've open-sourced, see py-image-compressor (batch image optimization CLI) and FastAPI DocShield (HTTP Basic auth for docs endpoints). Same philosophy, different problems.

Working on something similar?

I take on a handful of engagements at a time: architecture reviews, platform rescues, AI integration, and fractional technical leadership. The clearer the brief, the faster the reply.

How I work
Travel Panel WebSocket Server: real-time at uWebSockets.js speed
Travel Panel WebSocket Server: real-time at uWebSockets.js speed illustrationFeaturedMoon Holidays
3 minAug 2025 — Present

Travel Panel WebSocket Server: real-time at uWebSockets.js speed

High-performance WebSocket gateway for notifications, chat, and live state, running on port 8965 since August 2025

A high-throughput WebSocket server powering every live update across the Moon Holidays platform. Built on uWebSockets.js for raw performance, with MongoDB for persistence, Redis for pub/sub across pods, Firebase Auth for handshakes, and rate limits of 1000 msg/min in dev and 120 msg/min in production.

websocketuwebsocketsnodejstypescript
FastAPI DocShield: protect your API docs with one line
2 min13

FastAPI DocShield: protect your API docs with one line

HTTP Basic Auth on the OpenAPI docs endpoints for FastAPI

A tiny FastAPI extension that adds HTTP Basic Authentication to the Swagger UI, ReDoc, and OpenAPI JSON endpoints. Drop it in, set a username and password, and your API docs are no longer public. Useful when you want docs in production but not publicly indexable.

fastapipythonsecurityauthentication