A self-contained developer portal: sign up, generate API keys, browse interactive Swagger docs, fire live test requests, and watch a rate-limited public API in a usage-analytics dashboard.
- Accounts & sessions — email/password signup and login (bcrypt-hashed passwords, JWT sessions).
- API key lifecycle — generate, regenerate, and revoke keys from the dashboard. Keys are shown in full exactly once, at creation; only a SHA-256 hash and a display fragment are ever persisted.
- Rate limiting — a sliding-window limiter enforces 60 requests per
minute per API key by default (configurable via
.env). Exceeding it returns429 Too Many RequestswithRetry-AfterandX-RateLimit-*headers; every response includesX-RateLimit-Limit/-Remaining/-Reset. - Interactive API docs — a full OpenAPI 3.0 spec served through Swagger
UI at
/api-docs, with a built-in Authorize button so you can paste a key and try endpoints live. - Usage analytics — daily request volume, average response time, 429 counts, and a per-key breakdown, all backed by real request logs and rendered with Chart.js.
- API tester — a request builder in the dashboard to call the public API with any of your keys and watch rate-limit headers change in real time.
- Backend: Node.js + Express
- Auth: JSON Web Tokens (portal login) + hashed API keys (public API)
- Storage: a small file-backed JSON datastore (
/data) — no native database driver, sonpm installworks anywhere without a build toolchain. Swapsrc/config/db.jsfor Postgres/Mongo/etc. in production; nothing else in the app needs to change. - Docs:
swagger-ui-express+ a hand-writtenopenapi/openapi.yaml - Frontend: plain HTML/CSS/JS (no build step) + Chart.js via CDN
npm install
cp .env.example .env # adjust JWT_SECRET / rate limit if you'd like
npm run seed # To feed with the sample data
npm startThen open http://localhost:3000.
npm run seedThis creates a demo account ([email protected] / password123) with two
API keys and two weeks of realistic usage history, so the analytics charts
have something to show immediately. It's safe to run once; running it again
is a no-op if the account already has logs.
server.js Express app entry point
src/
config/db.js file-backed JSON datastore
middleware/
portalAuth.js JWT auth for the dashboard's own API
apiKeyAuth.js x-api-key auth for the public API
rateLimiter.js sliding-window limiter (60 req/min/key)
requestLogger.js logs every public-API call for analytics
models/ users, apiKeys, usageLogs
routes/
auth.routes.js /auth/signup, /auth/login, /auth/me
keys.routes.js /api/keys (CRUD, JWT-protected)
analytics.routes.js /api/analytics/* (JWT-protected)
publicApi.routes.js /api/v1/* (the rate-limited product API)
utils/
apiKey.js key generation + hashing
seedDemo.js `npm run seed` script
openapi/openapi.yaml OpenAPI 3.0 spec powering /api-docs
public/ dashboard frontend (HTML/CSS/vanilla JS)
data/ JSON "database" files (created on first run)
Base path: /api/v1. Every route requires an x-api-key header.
| Method | Path | Description |
|---|---|---|
| GET | /ping |
Health check |
| GET | /data |
List sample records |
| GET | /data/:id |
Get one sample record |
| POST | /echo |
Echoes the JSON body you send |
Example:
curl http://localhost:3000/api/v1/ping \
-H "x-api-key: devportal_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Response headers on every call:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1735689600
Once you exceed the limit within the current 60-second window:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{ "error": "rate_limit_exceeded", "message": "Rate limit of 60 requests per minute exceeded. Try again in 12s.", "limit": 60, "retryAfterSeconds": 12 }
All configuration lives in .env (see .env.example):
| Variable | Default | Description |
|---|---|---|
PORT |
3000 | Port the server listens on |
JWT_SECRET |
— | Secret used to sign portal login tokens |
RATE_LIMIT_MAX_REQUESTS |
60 | Requests allowed per window, per API key |
RATE_LIMIT_WINDOW_MS |
60000 | Window length in milliseconds |
This project ships with a tiny synchronous JSON-file datastore instead of a
real database so it runs anywhere with just npm install — no Postgres,
no native compiled modules, no Docker required. It's fine for local use and
small teams; for a real production deployment, swap src/config/db.js for
your database of choice (the rest of the app only calls the functions that
module exports, so nothing else needs to change) and move the in-memory
rate-limit buckets in src/middleware/rateLimiter.js to Redis if you run
more than one server process.





