Skip to content

Latest commit

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

API Gateway & Developer Platform

Production-grade distributed API gateway with microservice backend — built with Express, TypeScript, PostgreSQL, MongoDB, Redis, and Docker.

Architecture

┌──────────────┐ ┌──────────────────────────────────────────────────────────┐
│ Client │─────▶│ Gateway (:3000) │
└──────────────┘ │ Swagger → Context → Logger → Auth → API Key → Resolver │
│ → Rate Limiter → Validator → Cache → Routes → Forwarder │
│ → Event Emitter → Error Handler │
│ Socket.IO (/monitor) ◀── Alert Events │
└───────┬──────────────┬──────────────┬────────────────────┘
│ │ │
┌─────────▼──┐ ┌───────▼───────┐ ┌──▼────────────┐
│ Auth (:4001)│ │Project (:4002)│ │ Analytics │
│ Postgres │ │Postgres │ │ (:4003) │
│ JWT + │ │Projects │ │ MongoDB │
│ Refresh │ │API Keys │ │ Aggregation │
│ Rotation │ │Route Configs │ │ Alert Engine │
└────────────┘ └───────────────┘ └───────┬───────┘
│
┌────────────▼───────┐
│ Logging (:4004) │
│ MongoDB │
│ Structured Logs │
└────────────────────┘

Service Responsibilities

ServicePortDBResponsibility
Gateway3000RedisAPI gateway, auth, rate limiting, caching, routing, WebSocket events
Auth4001PostgreSQLUser registration, login, JWT access/refresh tokens
Project4002PostgreSQLProject CRUD, API key management, route configuration
Analytics4003MongoDBRequest analytics, aggregation pipelines, alert evaluation
Logging4004MongoDBLog ingestion, query, error retrieval

Quick Start

Prerequisites

  • Node.js 22+, npm 10+
  • Docker & Docker Compose
  • PostgreSQL 16 (or Docker)
  • MongoDB 7 (or Docker)
  • Redis 7 (or Docker)

Development

# 1. Install dependencies
npm ci
# 2. Generate Prisma client (from root schema)
npx prisma generate --schema=prisma/schema.prisma
# 3. Build shared package
npm run build --workspace=shared
# 4. Start infrastructure (PostgreSQL, MongoDB, Redis)
docker compose up -d postgres-auth postgres-project mongodb redis
# 5. Run database migrations
npx prisma migrate dev --schema=prisma/schema.prisma
# 6. Start services (in separate terminals or use concurrently)
npm run dev --workspace=@api-gateway/auth-service
npm run dev --workspace=@api-gateway/project-service
npm run dev --workspace=@api-gateway/analytics-service
npm run dev --workspace=@api-gateway/logging-service
npm run dev --workspace=@api-gateway/gateway
# 7. Run tests
npm test

Docker Production

# Build and start all services
docker compose up --build -d
# View logs
docker compose logs -f
# Health check
curl http://localhost:3000/health

Environment Variables

See .env.example for all configuration. Key variables:

VariableRequiredDefaultServices
ACCESS_TOKEN_SECRETYesGateway, Auth, Project
REFRESH_TOKEN_SECRETYesAuth
DATABASE_URLYesAuth, Project
MONGO_URINomongodb://localhost:27017/loggingAnalytics, Logging
REDIS_URLNoredis://localhost:6379Gateway
ALERT_SECRETNodev-alert-secret-change-in-productionGateway, Analytics

CORS

The gateway is configured with dynamic origin reflection for development:

  • Access-Control-Allow-Origin mirrors the requesting origin
  • Access-Control-Allow-Credentials: true
  • Allowed headers are dynamically read from the browser's preflight Access-Control-Request-Headers
  • Exposed headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Cache

No additional CORS configuration is needed for development. The Socket.IO server uses the same origin policy.

Rate Limiting

  • Algorithm: Sliding window via Redis sorted sets (ZREMRANGEBYSCORE / ZCARD / ZADD)
  • Window: 60 seconds
  • Tiers:
IdentifierDefault Limit
API Key1000 req/min
Authenticated user100 req/min
IP (unauthenticated)20 req/min
Per-route overrideVia route config rateLimit field

Response headers on every request:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1715299876

On exhaustion: 429 Too Many Requests with Retry-After: <seconds>.

Cache

  • Scope: GET requests only, when route config has cacheTTL > 0
  • Backend: Redis SETEX
  • Cache key:cache:GET:/path:<md5(query)>
  • Headers:X-Cache: HIT or X-Cache: MISS

API Reference

All requests go through the Gateway (http://localhost:3000), which forwards to backend services. Direct service access is also available on their respective ports.


Health

MethodPathAuthDescription
GET/healthPublicUnified health of gateway + all downstream services
curl http://localhost:3000/health
# → { "status": "ok", "service": "gateway", "timestamp": "...", "uptime": 123,# "services": [ { "name": "auth-service", "status": "ok", "latency": 2 }, ... ] }

Auth Service

All routes: POST /api/v1/auth/*

MethodPathAuthDescription
POST/api/v1/auth/registerPublicRegister a new user
POST/api/v1/auth/loginPublicLogin, returns access + refresh tokens
POST/api/v1/auth/refreshPublicRotate refresh token
POST/api/v1/auth/logoutBearerRevoke refresh token
GET/api/v1/auth/profileBearerGet authenticated user
POST/api/v1/auth/send-verification-emailPublicSend verification email
GET/api/v1/auth/verify-emailPublicVerify email with token
POST/api/v1/auth/forgot-passwordPublicRequest password reset email
POST/api/v1/auth/reset-passwordPublicReset password with token

Register

curl -X POST http://localhost:3000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"Str0ng!Pass","name":"John Doe"}'# → 201 { "message": "Registration successful",# "user": { "id": "...", "email": "...", "name": "..." } }

Login

curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"Str0ng!Pass"}'# → 200 { "user": {...}, "accessToken": "...", "refreshToken": "..." }

Refresh Token

curl -X POST http://localhost:3000/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken":"..."}'# → 200 { "accessToken": "...", "refreshToken": "..." }

Logout (authenticated)

curl -X POST http://localhost:3000/api/v1/auth/logout \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"refreshToken":"..."}'# → 200 { "message": "Logged out successfully" }

Profile (authenticated)

curl http://localhost:3000/api/v1/auth/profile \
-H "Authorization: Bearer <token>"# → 200 { "id": "...", "email": "...", "name": "..." }

Send Verification Email

curl -X POST http://localhost:3000/api/v1/auth/send-verification-email \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com"}'# → 200 { "message": "Verification email sent" }

Verify Email

curl "http://localhost:3000/api/v1/auth/verify-email?token=<verification-token>"# → 200 { "message": "Email verified successfully" }

Forgot Password

curl -X POST http://localhost:3000/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com"}'# → 200 { "message": "If email exists, reset link has been sent" }

Reset Password

curl -X POST http://localhost:3000/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{"token":"<reset-token>","newPassword":"NewSecurePass123!"}'# → 200 { "message": "Password reset successfully" }

Project Service

All routes require Authorization: Bearer <token>, except GET /api/v1/keys/verify.

Projects

MethodPathDescription
POST/api/v1/projectsCreate project
GET/api/v1/projectsList projects (page, limit, sort, order)
GET/api/v1/projects/:idGet project by ID
PUT/api/v1/projects/:idReplace a project
PATCH/api/v1/projects/:idUpdate a project
DELETE/api/v1/projects/:idDelete project
curl -X POST http://localhost:3000/api/v1/projects \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"My API","description":"My project"}'# → 201 { "id": "...", "name": "My API", ... }

API Keys

MethodPathDescription
POST/api/v1/projects/:projectId/keysCreate API key
GET/api/v1/projects/:projectId/keysList API keys
DELETE/api/v1/keys/:idRevoke API key
GET/api/v1/keys/verify?hash=<sha256>Verify API key hash
# Create key
curl -X POST http://localhost:3000/api/v1/projects/<projectId>/keys \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"Production Key"}'# → 201 { "id": "...", "key": "gw_...", "name": "Production Key", ... }# Verify key (no auth required)
curl "http://localhost:3000/api/v1/keys/verify?hash=<sha256-of-key>"# → { "valid": true, "key": { "id": "...", "projectId": "...", "userId": "..." } }

Route Configs

MethodPathDescription
POST/api/v1/projects/:projectId/routesCreate route config
GET/api/v1/projects/:projectId/routesList route configs
GET/api/v1/routes/:idGet route config by ID
PATCH/api/v1/routes/:idUpdate route config
DELETE/api/v1/routes/:idDelete route config
GET/api/v1/routesGet all active route configs (no auth)
curl -X POST http://localhost:3000/api/v1/projects/<projectId>/routes \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"method":"GET","path":"/users/:id","service":"auth-service","rateLimit":100}'# → 201 { "id": "...", "method": "GET", "path": "/users/:id", ... }

Logging Service

MethodPathAuthDescription
POST/api/v1/logsPublicIngest single log entry
POST/api/v1/logs/batchPublicIngest multiple log entries
GET/api/v1/logsPublicQuery logs (page, limit, userId, statusCode, method, from, to)
GET/api/v1/logs/errorsPublicGet error log entries
GET/api/v1/logs/:requestIdPublicGet log by request ID
# Ingest a log
curl -X POST http://localhost:3000/api/v1/logs \
-H "Content-Type: application/json" \
-d '{"requestId":"550e8400-e29b-41d4-a716-446655440000","method":"GET","path":"/api/test","statusCode":200,"latency":42,"ip":"127.0.0.1"}'# → 201 { "message": "Log ingested" }# Query logs
curl "http://localhost:3000/api/v1/logs?from=2026-01-01T00:00:00Z&statusCode=200&page=1&limit=20"

Analytics Service

All analytics endpoints require Authorization: Bearer <token>.

MethodPathAuthDescription
GET/api/v1/analytics/summaryBearerAggregate summary (totals, latency, error rate)
GET/api/v1/analytics/requests-over-timeBearerRequest count bucketed by hour/day
GET/api/v1/analytics/error-rateBearerError rate
GET/api/v1/analytics/latencyBearerLatency percentiles (p50, p95, p99)
GET/api/v1/analytics/top-endpointsBearerMost requested endpoints
GET/api/v1/analytics/top-usersBearerMost active users
GET/api/v1/analytics/api-key-usageBearerAPI key usage stats

Common query parameters (all optional):

  • from / to — ISO datetime range (all endpoints)
  • intervalhour or day (only /requests-over-time)
  • limit — max results, default 10 (only top-* endpoints)
curl "http://localhost:3000/api/v1/analytics/summary?from=2026-01-01T00:00:00Z" \
-H "Authorization: Bearer <token>"# → { "totalRequests": 1234, "avgLatency": 45.2, "p95Latency": 120, "totalErrors": 31, ... }
curl "http://localhost:3000/api/v1/analytics/latency" \
-H "Authorization: Bearer <token>"# → { "p50": 35, "p95": 120, "p99": 350, "avg": 42.1 }

Alert Management

All alert CRUD endpoints require Authorization: Bearer <token>.

MethodPathAuthDescription
POST/api/v1/alerts/emitX-Alert-SecretBridge alert event to Socket.IO
POST/api/v1/alerts/rulesBearerCreate alert rule
GET/api/v1/alerts/rulesBearerList alert rules
GET/api/v1/alerts/rules/:idBearerGet alert rule
PUT/api/v1/alerts/rules/:idBearerUpdate alert rule
DELETE/api/v1/alerts/rules/:idBearerDelete alert rule
GET/api/v1/alerts/eventsBearerList alert events
PUT/api/v1/alerts/events/:id/acknowledgeBearerAcknowledge alert

Metrics: request_count, error_rate, latency_p50, latency_p95, latency_p99, uptime

Operators: gt, gte, lt, lte, eq

# Create alert rule
curl -X POST http://localhost:3000/api/v1/alerts/rules \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name":"High error rate","service":"gateway","metric":"error_rate","windowSeconds":300,"threshold":5,"operator":"gt","coolDownSeconds":600}'# → 201 { "id": "...", "name": "High error rate", ... }# List events
curl "http://localhost:3000/api/v1/alerts/events?severity=critical&limit=10" \
-H "Authorization: Bearer <token>"# → { "events": [...], "total": 3, "limit": 10, "offset": 0 }

Socket.IO Events

Connect to http://localhost:3000/monitor namespace:

EventDirectionDescription
log:entryServer → ClientReal-time request log entry
alert:newServer → ClientAlert triggered
connections:activeServer → ClientActive connection count update
constsocket=io('http://localhost:3000/monitor');socket.on('alert:new',(data)=>console.log('Alert:',data));socket.on('log:entry',(data)=>console.log('Request:',data.method,data.path));

Middleware Pipeline (Gateway)

The gateway applies middleware in this exact order for every request:

StepMiddlewareDescription
1Swagger UIServes OpenAPI docs at /api-docs
2Request ContextInjects requestId (UUID) and startTime
3LoggerRequests logging with correlation ID
4AuthenticatorJWT Bearer token verification (public paths skip)
5API Key ValidatorSHA-256 hashed key → Redis cache → project-service verify
6Route Config ResolverLoads per-route config (rate limit, cache TTL) from in-memory cache
7Rate LimiterRedis sliding window (sorted sets), per-route limits
8Request ValidatorZod schema validation by METHOD:path
9Response CacherGET-only, Redis SETEX with res.json override on miss
10ForwarderProxies matched routes to backend services; falls through to 404
11Event EmitterOn res.finish — async POST to logging-service + Socket.IO emit
12Error HandlerCatches all errors, returns unified JSON envelope

Project Structure

├── prisma/ # Unified Prisma schema
│ └── schema.prisma # User, RefreshToken, Project, ApiKey, RouteConfig
├── shared/ # Shared package (@api-gateway/shared)
│ └── src/
│ ├── constants/
│ ├── errors/ # AppError, HTTP error classes
│ └── types/ # TypeScript interfaces
├── gateway/ # Express API gateway
│ ├── src/
│ │ ├── middleware/ # Pipeline (12 middleware)
│ │ ├── proxy/ # HTTP forwarder
│ │ ├── routes/ # Health, alert emit
│ │ ├── services/ # HTTP client, route config, Socket.IO holder
│ │ ├── app.ts # Express app assembly
│ │ ├── server.ts # HTTP + Socket.IO server
│ │ └── redis.ts # Redis connection
│ └── tests/
├── services/
│ ├── auth-service/ # Auth (register/login/refresh/logout/profile)
│ ├── project-service/ # Projects, API keys, route configs
│ ├── analytics-service/ # Analytics, alerts, health checks
│ └── logging-service/ # Log ingestion and querying
└── docker-compose.yml # Full stack deployment

Testing

# All tests
npm test# Individual services
npx jest --config gateway/jest.config.js
npx jest --config services/auth-service/jest.config.js
npx jest --config services/project-service/jest.config.js
npx jest --config services/logging-service/jest.config.js
# End-to-end tests
node e2e-test.mjs

Test stack: Jest + ts-jest + supertest. Mocks: Prisma, Mongoose, ioredis, bcrypt.

Railway Deployment

One-time setup

  1. Push the repo to GitHub
  2. Create a Railway account and install the GitHub integration
  3. Create a new Railway project → Deploy from GitHub repo → select this repo
  4. Add each service as a separate Railway service within the project:
ServiceDockerfilePortHealthcheckPlugins needed
auth-serviceservices/auth-service/Dockerfile4001/healthPostgreSQL
project-serviceservices/project-service/Dockerfile4002/healthPostgreSQL
analytics-serviceservices/analytics-service/Dockerfile4003/healthMongoDB
logging-serviceservices/logging-service/Dockerfile4004/healthMongoDB
gatewaygateway/Dockerfile3000/healthRedis
  1. In each service's Railway dashboard tab, set:

    • Root Directory: / (repo root)
    • Build Command: (leave empty — Dockerfile handles it)
    • Start Command: (leave empty — Dockerfile handles it)
  2. Add Railway Plugins to the project:

    • PostgreSQL — generates DATABASE_URL (auth-service, project-service share this via Railway service variables)
    • MongoDB — add external MongoDB Atlas or use Railway's MongoDB plugin
    • Redis — generates REDIS_URL (gateway)

Environment variables

Set these in each service's Railway dashboard (or use Shared Variables in Railway project settings):

VariableServicesSource
ACCESS_TOKEN_SECRETAllGenerate a random 32+ char string
REFRESH_TOKEN_SECRETAuthGenerate a random 32+ char string
DATABASE_URLAuth, ProjectRailway PostgreSQL plugin provides this
MONGO_URIAnalytics, LoggingExternal MongoDB Atlas or Railway plugin
REDIS_URLGatewayRailway Redis plugin provides this
AUTH_SERVICE_URLGateway, Analyticshttps://auth-service.<railway-domain>
PROJECT_SERVICE_URLGateway, Analyticshttps://project-service.<railway-domain>
ANALYTICS_SERVICE_URLGatewayhttps://analytics-service.<railway-domain>
LOGGING_SERVICE_URLGateway, Analyticshttps://logging-service.<railway-domain>

Each service gets ${{<service-name>.RAILWAY_PUBLIC_DOMAIN}} from Railway after it deploys — use Railway's variable referencing to wire them together.

Note: The .railway/*.toml files in this repo define the Dockerfile paths for each service — Railway reads these automatically when you connect the repo.

Docker Build

# Build all images
docker compose build
# Verify images
docker images "apigateway-*"# Run full stack
docker compose up -d

Monitoring

  • Real-time request streaming via Socket.IO (/monitor namespace) — events log:entry and alert:new
  • Alert evaluation every 30s (configurable via ALERT_INTERVAL_MS)
  • Alert events broadcast on Socket.IO as alert:new
  • Full system health: GET /health

Error Response Format

All errors return a consistent JSON envelope:

{
"error": "ERROR_CODE",
"message": "Human-readable message",
"statusCode": 400,
"timestamp": "2026-05-10T00:00:00.000Z",
"requestId": "uuid"
}
HTTPCodeMeaning
400VALIDATION_ERRORRequest body/params/query failed Zod validation
401UNAUTHORIZEDMissing or invalid Bearer token
401TOKEN_EXPIREDAccess token expired
403FORBIDDENAccount deactivated
404NOT_FOUNDResource not found
409CONFLICTResource already exists
429TOO_MANY_REQUESTSRate limit exceeded

About

Distributed API Gateway with microservice backend. Provides authentication, project management, API key verification, rate limiting, caching, logging, analytics, and real-time monitoring via Socket.IO.

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages