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
26 changes: 24 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@
"packages": [
"packages/*"
],
"nohoist": []
"nohoist": [],
"catalog": {
"eslint": "^9.13.0",
"prettier": "^3.3.3",
"prisma": "^6.5.0",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"zod": "^4.0.10",
"concurrently": "^9.1.2",
"tsx": "^4.19.2",
"ts-jest": "^29.2.6",
"ts-node": "^10.9.2",
"jest": "^29.7.0",
"vitest": "^2.1.8",
"unplugin-swc": "^1.5.5"
}
},
"scripts": {
"build": "pnpm lint && turbo run build",
Expand All @@ -32,11 +47,18 @@
},
"devDependencies": {
"@biomejs/biome": "^2.1.3",
"@elysiajs/trpc": "^1.1.0",
"elysia": "^1.4.16",
"turbo": "^2.5.5",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=14.16.0"
},
"packageManager": "pnpm@9.15.2"
"packageManager": "pnpm@9.15.2",
"pnpm": {
"overrides": {
"@sinclair/typebox": "^0.34.0"
}
}
}
245 changes: 245 additions & 0 deletions packages/trpc-benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# @goatlab/trpc-benchmarks

Performance benchmarks for tRPC APIs comparing **Express + Node.js** vs **Hono + Bun**.

## Prerequisites

### Required

- **Node.js** >= 20.0.0
- **k6** - Load testing tool ([installation guide](https://k6.io/docs/getting-started/installation/))

```bash
# macOS
brew install k6

# Ubuntu/Debian
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

# Windows
choco install k6
```

### Optional (for Bun benchmarks)

- **Bun** - Fast JavaScript runtime ([installation guide](https://bun.sh/docs/installation))

```bash
curl -fsSL https://bun.sh/install | bash
```

## Installation

```bash
cd packages/trpc-benchmarks
pnpm install
```

## Quick Start

### Run All Benchmarks

Run both Express+Node and Hono+Bun benchmarks sequentially:

```bash
pnpm bench:all
```

With custom options:

```bash
# More virtual users and longer duration
npx tsx src/runners/run-all-benchmarks.ts --vus 50 --duration 60s

# Quick mode (simplified scenarios)
npx tsx src/runners/run-all-benchmarks.ts --quick
```

### Run Individual Benchmarks

```bash
# Express + Node.js only
pnpm bench:k6:express

# Hono + Bun only
pnpm bench:k6:hono
```

### Compare Results

After running benchmarks, compare saved results:

```bash
pnpm bench:compare
```

## Manual Testing

### Start Servers Independently

```bash
# Express + Node.js (port 3001)
pnpm dev:express

# Hono + Bun (port 3002) - requires Bun
pnpm dev:hono
```

### Run k6 Directly

```bash
# Against Express server
k6 run --vus 10 --duration 30s --env BASE_URL=http://localhost:3001 src/k6/benchmark.js

# Against Hono server
k6 run --vus 10 --duration 30s --env BASE_URL=http://localhost:3002 src/k6/benchmark.js

# Quick benchmark (simpler scenarios)
k6 run --vus 10 --duration 30s --env BASE_URL=http://localhost:3001 src/k6/quick-benchmark.js
```

## Benchmark Scenarios

### Full Benchmark (`benchmark.js`)

Three test scenarios run sequentially:

1. **Smoke Test** (10s) - Basic functionality validation
- Health check, ping, server info

2. **Load Test** (90s) - Sustained normal load
- Ramps from 0 → 10 → 20 VUs
- Mix of queries and mutations
- User CRUD operations
- Paginated list queries
- Light computation

3. **Stress Test** (70s) - Find breaking points
- Ramps up to 100 VUs
- Rapid-fire requests
- Batch operations
- Large data transfers
- CPU-intensive computation

### Quick Benchmark (`quick-benchmark.js`)

Single scenario for rapid comparison:
- Ping endpoint (minimal overhead)
- User queries with input validation
- Mutations (create operations)
- Paginated list queries

## API Endpoints

The shared tRPC router includes these endpoints:

| Endpoint | Type | Description |
|----------|------|-------------|
| `ping` | Query | Simple "pong" response |
| `health` | Query | Health check with timestamp |
| `info` | Query | Runtime info (node/bun version) |
| `user.get` | Query | Get user by ID |
| `user.create` | Mutation | Create new user |
| `user.list` | Query | List all users |
| `user.batch` | Query | Batch get users |
| `items.list` | Query | Paginated items with filtering |
| `items.all` | Query | All items (large response) |
| `items.count` | Query | Item count |
| `compute.fibonacci` | Query | CPU-bound calculation |
| `compute.isPrime` | Query | Prime number check |
| `compute.hash` | Query | Simulated work |
| `compute.sort` | Query | Array sorting benchmark |
| `echo` | Mutation | Echo payload back |

## Metrics

k6 collects the following custom metrics:

- `trpc_ping_latency` - Ping endpoint latency
- `trpc_health_latency` - Health check latency
- `trpc_user_get_latency` - User query latency
- `trpc_user_create_latency` - User mutation latency
- `trpc_items_list_latency` - List query latency
- `trpc_compute_latency` - Computation latency
- `trpc_error_rate` - Error rate percentage
- `trpc_requests` - Total request count

### Thresholds

Default pass/fail thresholds:
- 95th percentile < 500ms
- 99th percentile < 1000ms
- Ping P95 < 50ms
- Error rate < 1%

## Output

Results are saved to `results/` directory:

- `benchmark-{timestamp}.txt` - Human-readable summary
- `benchmark-{timestamp}.json` - Machine-readable data

## CLI Options

### `run-all-benchmarks.ts`

| Option | Default | Description |
|--------|---------|-------------|
| `--vus` | 10 | Number of virtual users |
| `--duration` | 30s | Test duration |
| `--quick` | false | Use simplified quick benchmark |
| `--output` | results | Output directory |

### `run-express-benchmark.ts` / `run-hono-benchmark.ts`

| Option | Default | Description |
|--------|---------|-------------|
| `--vus` | 10 | Number of virtual users |
| `--duration` | 30s | Test duration |
| `--quick` | false | Use simplified quick benchmark |

### `compare-results.ts`

| Option | Default | Description |
|--------|---------|-------------|
| `--dir` | results | Results directory to read from |

## Example Output

```
================================================================================
tRPC API Benchmark Results
================================================================================

Timestamp: 2024-01-15T10:30:00.000Z

--------------------------------------------------------------------------------
Summary Comparison
--------------------------------------------------------------------------------

Server Avg (ms) P95 (ms) P99 (ms) Requests Req/s
-------------------------------------------------------------------------------------
Express + Node.js 2.45 5.12 8.34 15234 507
Hono + Bun 1.23 2.56 4.12 28456 948

--------------------------------------------------------------------------------
Performance Comparison
--------------------------------------------------------------------------------

Hono + Bun is 49.8% faster (avg latency)
Hono + Bun has 50.0% better P95 latency
Hono + Bun handled 86.8% more requests

================================================================================
```

## Notes

- Benchmarks should be run on a quiet system for accurate results
- Results may vary based on hardware, OS, and system load
- The Hono+Bun benchmark requires Bun to be installed for accurate comparison
- If Bun is not available, Hono will run on Node.js (not representative of Bun performance)
54 changes: 54 additions & 0 deletions packages/trpc-benchmarks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@goatlab/trpc-benchmarks",
"version": "0.1.0",
"private": true,
"description": "Performance benchmarks for tRPC APIs: Express+Node vs Hono+Bun vs Elysia+Bun",
"type": "module",
"scripts": {
"build": "tsc",
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:seed": "tsx src/db/seed.ts",
"db:reset": "rm -f prisma/benchmark.db && prisma db push && tsx src/db/seed.ts",
"dev:express": "tsx src/servers/express-server.ts",
"dev:hono": "bun run src/servers/hono-server.ts",
"dev:elysia": "bun run src/servers/elysia-server.ts",
"bench:k6": "k6 run src/k6/benchmark.js",
"bench:k6:express": "tsx src/runners/run-express-benchmark.ts",
"bench:k6:hono": "tsx src/runners/run-hono-benchmark.ts",
"bench:k6:elysia": "tsx src/runners/run-elysia-benchmark.ts",
"bench:all": "tsx src/runners/run-all-benchmarks.ts",
"bench:compare": "tsx src/runners/compare-results.ts",
"bench:realistic": "tsx src/runners/run-realistic-benchmark.ts",
"lint": "biome check --write .",
"format": "biome format --write ."
},
"dependencies": {
"@prisma/client": "^5.22.0",
"@trpc/client": "^11.4.3",
"@trpc/server": "^11.4.3",
"chalk": "^4.1.2",
"commander": "^12.0.0",
"express": "^5.1.0",
"superjson": "^2.2.2",
"zod": "^3.24.4"
},
"devDependencies": {
"@biomejs/biome": "^2.1.3",
"@goatlab/biome": "workspace:*",
"@hono/node-server": "^1.14.0",
"@hono/trpc-server": "^0.3.4",
"@types/express": "^5.0.0",
"@types/node": "^22.15.21",
"@sinclair/typebox": "^0.34.0",
"bun-types": "^1.2.0",
"elysia": "^1.2.0",
"hono": "^4.7.9",
"prisma": "^5.22.0",
"tsx": "^4.7.1",
"typescript": "^5.6.3"
},
"engines": {
"node": ">=20.0.0"
}
}
Binary file added packages/trpc-benchmarks/prisma/benchmark.db
Binary file not shown.
Loading
Loading