Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .env.benchmark.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
NODE_ENV=benchmark
JWT_SECRET=benchmark-secret

# Optional: benchmark an already-running API instead of starting apps/api locally.
# BENCHMARK_BASE_URL=http://127.0.0.1:4000

# Optional: use a pre-issued bearer token for protected endpoints.
# BENCHMARK_AUTH_TOKEN=

# Defaults are intentionally modest so local and CI runs are repeatable.
BENCHMARK_ITERATIONS=30
BENCHMARK_CONCURRENCY=4
BENCHMARK_WARMUP=2
BENCHMARK_RESULTS_DIR=benchmarks/results

# Local benchmark runs disable the global rate limiter by default so route metrics
# are not dominated by 429 responses from a single loopback IP.
BENCHMARK_DISABLE_RATE_LIMIT=true
24 changes: 24 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: API benchmark smoke

on:
pull_request:
paths:
- "apps/api/**"
- "benchmarks/**"
- "package*.json"
- ".github/workflows/benchmark.yml"
push:
branches:
- main

jobs:
benchmark-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run benchmark:smoke
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ node_modules
dist
.env
.env.*
!.env.benchmark.example
coverage
*.log
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"dev": "node src/server.js",
"start": "node src/server.js",
"test": "node --test src/tests"
"test": "node --test \"src/tests/**/*.test.js\""
},
"dependencies": {
"cors": "^2.8.5",
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/middleware/rateLimit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import rateLimit from "express-rate-limit";
export const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 200,
skip: () => process.env.BENCHMARK_DISABLE_RATE_LIMIT === "true",
standardHeaders: "draft-7",
legacyHeaders: false
});
22 changes: 22 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# API Benchmarks

Run the full local API benchmark from the repository root:

```sh
npm run benchmark
```

Run the CI-sized smoke benchmark:

```sh
npm run benchmark:smoke
```

The runner starts `apps/api` on an ephemeral local port unless `BENCHMARK_BASE_URL`
is set. Copy `.env.benchmark.example` to `.env.benchmark` to override iteration
counts, concurrency, auth token, results directory, or a remote target URL.

Each run writes both JSON and Markdown reports to `benchmarks/results`. The
threshold gate is configured in `benchmarks/thresholds.json` and fails the
process when aggregate p95 latency, aggregate error rate, aggregate RPS, or an
endpoint-specific threshold falls outside the configured limits.
4 changes: 4 additions & 0 deletions benchmarks/results/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
benchmark-*.json
benchmark-*.md
!.gitkeep
!.gitignore
1 change: 1 addition & 0 deletions benchmarks/results/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading
Loading