Skip to content

Latest commit

 

History

History
403 lines (295 loc) · 12.7 KB

File metadata and controls

403 lines (295 loc) · 12.7 KB

Contributing to RateLock

Thanks for your interest in contributing. RateLock is an API rate limiter as a service — a Redis-backed engine, a REST API, a dashboard, and drop-in SDKs. This guide covers how to get the project running, how the codebase is laid out, and what we look for in a pull request.

New here? Issues labelled good first issue are scoped to be completable without deep knowledge of the whole system.


Table of Contents


Code of Conduct

Be respectful and constructive. Assume good faith, critique the code rather than the person, and keep discussion focused on the technical merits. Harassment or personal attacks are not tolerated.


Ways to Contribute

You don't need to write code to help:

  • Report bugs with clear reproduction steps
  • Improve documentation — the README, SDK docs, or in-code comments
  • Write an SDK for a language we don't cover yet (Go, Ruby, PHP, Rust…)
  • Improve the dashboard UI or accessibility
  • Add tests — the project currently has no test suite, so this is high-impact
  • Triage issues by reproducing reports and adding detail

Getting Set Up

Prerequisites

  • Docker and Docker Compose
  • Node.js 24+ (for running services outside Docker)
  • Python 3.10+ (only if working on the Python SDK)

Option 1 — Full stack in Docker (fastest)

git clone git@github.com:TheCodeHeist-Coder/ratelock.git
cd ratelock

# Optional: override defaults. Compose works without this file.
cp .env.example .env

docker compose up -d --build

Once healthy:

Service URL
Dashboard http://localhost:8080
Backend API http://localhost:4000
Health check http://localhost:4000/health

Migrations are applied automatically when the backend container boots. Create an account from the dashboard's Sign up page, then add a project to get an API key.

Option 2 — Local dev (recommended when changing code)

Run Postgres and Redis in Docker, everything else on your machine so you get hot reload:

# Infrastructure only
docker compose up db redis -d

# Backend — terminal 1
cd backend
cp .env.example .env
npm install
npx prisma generate
npx prisma migrate dev
npm run dev              # API on :4000

# Frontend — terminal 2
cd frontend
npm install
npm run dev              # Vite on :5173, proxies /api → :4000

Note on Redis: compose does not publish Redis to the host — the backend reaches it as redis:6379 over the compose network. For local backend dev, backend/.env points at localhost:6379, so either add ports: ["6379:6379"] to the redis service or run a local Redis.


Project Layout

ratelock/
├── backend/              Express + TypeScript API and rate-limit engine
│   ├── prisma/
│   │   ├── schema.prisma     Data model (User, Project, Rule, Event, Alert)
│   │   └── migrations/
│   └── src/
│       ├── app.ts            Express app, middleware, route mounting
│       ├── index.ts          Server entry point
│       ├── routes/           Thin routers — no business logic here
│       ├── controller/       Request/response handling, validation
│       ├── services/         Business logic (rateLimitService, analyticsService)
│       ├── middleware/       auth (JWT), projectOwner (ownership checks)
│       ├── config/redis.ts   ioredis client
│       ├── lib/prisma.ts     Prisma client singleton
│       └── generated/        Prisma client output — never edit by hand
│
├── frontend/             React 19 + Vite + Tailwind v4 dashboard
│   └── src/
│       ├── screens/          Route-level pages
│       ├── components/       Shared UI
│       │   ├── charts/       AreaChart, Sparkline
│       │   └── dashboard/    DashboardLayout (sidebar shell)
│       ├── stores/           Zustand stores (authStore, projectStore)
│       ├── lib/api.ts        Axios instance, JWT interceptors
│       └── index.css         Tailwind theme tokens + component primitives
│
├── sdk/
│   ├── node/             @ratelock/express middleware
│   └── python/           ratelock package (FastAPI/Flask middleware)
│
├── nginx/                Serves the built frontend, proxies /api → backend
└── docker-compose.yml    db, redis, backend, web

Layering rule (backend)

Keep the boundaries clean — this is the convention most PRs get feedback on:

route  →  controller  →  service  →  prisma / redis
  • Routes only wire paths to controllers.
  • Controllers parse input, call a service, shape the response.
  • Services hold the business logic and own all Prisma/Redis access.

Don't call Prisma directly from a controller.


How the Hot Path Works

The most performance-sensitive code is the check endpoint that SDKs call on every inbound request to a protected API:

POST /api/rl/check
Headers:
  X-RL-Key       <project api key>
  X-RL-Endpoint  <path>        (default "/")
  X-RL-Method    <method>      (default "GET")
  X-RL-IP        <client ip>   (optional)

It lives in backend/src/services/rateLimitService.ts, reached via routes/rateLimit.ts → controller/rateLimitController.ts.

Because it runs on every request, treat it as latency-critical:

  • Redis is the source of truth for counters — never add a Postgres read to this path
  • Avoid extra round trips; batch Redis operations where you can
  • Event writes for analytics must not block the allow/deny decision
  • Benchmark before and after if you touch it

Everything under /api/v1/* (auth, projects, rules, alerts) is the control plane and is not latency-critical in the same way.


Development Workflow

  1. Fork the repo and create a branch off dev:

    git checkout dev
    git pull origin dev
    git checkout -b fix/sliding-window-off-by-one

    Branch naming: feat/…, fix/…, docs/…, refactor/…, test/…

  2. Make your change, keeping it focused. One concern per PR.

  3. Verify it builds and passes lint (see Testing Your Changes).

  4. Commit with a clear message, then push and open a PR against dev.

Target dev, not main. main tracks released state.


Coding Conventions

TypeScript (both backend and frontend)

  • ES modules throughout. The backend is "type": "module" — relative imports need the .js extension even in .ts files:
    import { prisma } from '../lib/prisma.js';   // correct
    import { prisma } from '../lib/prisma';      // breaks at runtime
  • Prefer explicit types on exported functions; let inference handle locals.
  • Avoid any. If you genuinely need an escape hatch, add a comment explaining why.

Comments

Write comments that explain why, not what. The existing code follows this — match it. Skip comments that restate the line below them.

// Redis is the source of truth here; a Postgres read would add ~15ms
// to a path that runs on every customer request.
const count = await redis.incr(key);

Frontend

  • Tailwind v4 with the theme defined in frontend/src/index.css under @theme. Use the semantic tokens (brand-*, ink-*, surface-*) rather than raw hex values so the palette stays changeable in one place.
  • Reuse the component primitives already defined in index.css — .card, .btn-primary, .input, .badge, .panel-head, .segmented — instead of re-styling from scratch.
  • Charts live in components/charts/. AreaChart and Sparkline are generic; extend them rather than writing a one-off SVG.
  • The landing page keeps its neon-green identity; the dashboard and auth pages use the blue theme. Don't unify them without discussion.
  • State goes in Zustand stores under stores/, not in prop-drilled component state.

API responses

Follow the existing shapes. Errors go through utils/errot.ts's errorResponse helper. (Yes, the filename has a typo — renaming it is a welcome standalone PR.)


Working with the Database

Schema lives in backend/prisma/schema.prisma. After editing it:

cd backend
npm run generate      # regenerate the Prisma client
npm run migrate       # create + apply a migration (prompts for a name)

Available scripts (backend/package.json):

Script Runs
npm run build tsc -b
npm run dev build, then start dist/index.js
npm run start start dist/index.js
npm run generate prisma generate
npm run migrate prisma migrate dev
npm run deploy prisma migrate deploy (production)

The README's "Prisma Commands" section currently lists db:generate, db:migrate, db:seed and db:studio. Those scripts do not exist — use the table above. There is also no seed script yet; adding one is a genuinely useful contribution.

Never edit backend/src/generated/ — it is Prisma output and is regenerated on every npm run generate.

Migrations are committed to the repo. Don't hand-edit an applied migration; create a new one.


Testing Your Changes

There is no automated test suite yet. Until there is, verify manually and say what you checked in your PR description.

Both packages must build and lint cleanly:

# Backend
cd backend && npm run build

# Frontend
cd frontend && npm run build && npm run lint

Manual checks by area:

  • Rate-limit engine — hit POST /api/rl/check with a real API key and confirm allow/deny flips at the configured limit:
    curl -X POST http://localhost:4000/api/rl/check \
      -H "X-RL-Key: <your project api key>" \
      -H "X-RL-Endpoint: /api/test" \
      -H "X-RL-Method: GET"
  • Dashboard — click through the affected screens at both desktop and mobile widths; the app is expected to be responsive.
  • Charts — confirm hover tooltips and the crosshair still track correctly, and that empty/no-data states render.
  • Auth — sign up, log in, log out, and confirm protected routes redirect.

Adding a real test setup (Vitest or Jest) is high on the wish list — see Ways to Contribute.


Commit Messages

Write in the imperative mood and describe the effect of the change:

fix: correct off-by-one in sliding window boundary
feat: add Go SDK with net/http middleware
docs: document the rate-limit check headers
refactor: move analytics aggregation into a service

Keep the subject under ~72 characters. If the change needs context, add a body explaining the reasoning.


Opening a Pull Request

Before you open it:

  • Branch is off dev and targets dev
  • npm run build passes in both backend/ and frontend/
  • npm run lint passes in frontend/
  • No stray debug logging or commented-out code
  • No secrets, .env files, or credentials committed
  • src/generated/ is untouched
  • Docs updated if you changed behaviour, env vars, or the API

In the PR description, include:

  1. What changed and why
  2. How you verified it — the manual steps you ran
  3. Screenshots or a short clip for any UI change (before/after is ideal)
  4. Linked issue, e.g. Closes #42

Small, focused PRs get reviewed faster. If you're planning something large, open an issue first so we can agree on the approach before you invest the time.


Reporting Bugs

Open an issue with:

  • What you expected versus what happened
  • Exact reproduction steps
  • Environment: OS, Node version, Docker or local setup
  • Relevant logs (docker compose logs backend) — redact API keys and tokens

For anything security-sensitive, please report it privately to the maintainer rather than opening a public issue.


Proposing Features

Open an issue describing:

  • The problem you're solving and who has it
  • Your proposed approach
  • Alternatives you considered

Worth discussing before building: changes to the rate-limit algorithms, the database schema, the public SDK surface, or the hot path.


Questions

If something in this guide is unclear or wrong, that's a bug in the docs — open an issue or send a PR fixing it.