From 913e4784595962c9e7a0525ac6881a0d015afedc Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Fri, 14 Aug 2026 07:45:30 +0200 Subject: [PATCH 1/2] Add nestjs framework --- frameworks/nestjs/Dockerfile | 20 +++++ frameworks/nestjs/README.md | 26 ++++++ frameworks/nestjs/meta.json | 19 +++++ frameworks/nestjs/package.json | 18 ++++ frameworks/nestjs/src/app.controller.ts | 104 ++++++++++++++++++++++++ frameworks/nestjs/src/app.module.ts | 7 ++ frameworks/nestjs/src/main.ts | 35 ++++++++ frameworks/nestjs/tsconfig.json | 14 ++++ 8 files changed, 243 insertions(+) create mode 100644 frameworks/nestjs/Dockerfile create mode 100644 frameworks/nestjs/README.md create mode 100644 frameworks/nestjs/meta.json create mode 100644 frameworks/nestjs/package.json create mode 100644 frameworks/nestjs/src/app.controller.ts create mode 100644 frameworks/nestjs/src/app.module.ts create mode 100644 frameworks/nestjs/src/main.ts create mode 100644 frameworks/nestjs/tsconfig.json diff --git a/frameworks/nestjs/Dockerfile b/frameworks/nestjs/Dockerfile new file mode 100644 index 000000000..f19f4ef18 --- /dev/null +++ b/frameworks/nestjs/Dockerfile @@ -0,0 +1,20 @@ +FROM node:26-trixie-slim AS build +RUN apt-get update && \ + apt-get install -y --no-install-recommends ca-certificates && \ + rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY package.json tsconfig.json ./ +RUN npm install +COPY src ./src +RUN npx tsc && npm prune --omit=dev + +FROM node:26-trixie-slim +RUN apt-get update && \ + apt-get install -y --no-install-recommends ca-certificates curl && \ + rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist +ENV NODE_ENV=production +EXPOSE 8080 +CMD ["node", "dist/main.js"] diff --git a/frameworks/nestjs/README.md b/frameworks/nestjs/README.md new file mode 100644 index 000000000..e598effe7 --- /dev/null +++ b/frameworks/nestjs/README.md @@ -0,0 +1,26 @@ +# nestjs + +NestJS 11 on the Express platform adapter, default configuration. + +## Stack + +- **Language:** TypeScript 5.9 on Node 26 +- **Framework:** NestJS 11 (`@nestjs/platform-express`) +- **Build:** Two-stage, `node:26-trixie-slim` runtime + +## Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/pipeline` | GET | Returns `ok` (plain text) | +| `/baseline11` | GET | Sums query parameter values | +| `/baseline11` | POST | Sums query parameters + request body | +| `/json/{count}?m=N` | GET | First `count` dataset items with `total = price * quantity * m` | +| `/upload` | POST | Reads the body and returns the byte count | + +## Notes + +- Controller with the Nest routing and `@Param` / `@Query` / `@Header` decorators +- Compression through the `compression` middleware, as the Nest docs recommend +- Body parsers are off and the POST endpoints read the raw stream, since they only sum or count what arrives +- The cluster module is used for multi-core scaling, one worker per available CPU diff --git a/frameworks/nestjs/meta.json b/frameworks/nestjs/meta.json new file mode 100644 index 000000000..d41910c82 --- /dev/null +++ b/frameworks/nestjs/meta.json @@ -0,0 +1,19 @@ +{ + "display_name": "nestjs", + "language": "TS", + "type": "flagship", + "mode": "standard", + "engine": "nodehttp", + "description": "NestJS 11 on the Express platform adapter, default configuration. Routing and parameter decorators through the Nest API, controller return values serialized by the framework, gzip through the compression middleware the Nest docs recommend.", + "repo": "https://github.com/nestjs/nest", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn", + "json", + "json-comp", + "upload" + ], + "maintainers": [] +} diff --git a/frameworks/nestjs/package.json b/frameworks/nestjs/package.json new file mode 100644 index 000000000..6a89e3cca --- /dev/null +++ b/frameworks/nestjs/package.json @@ -0,0 +1,18 @@ +{ + "name": "httparena-nestjs", + "private": true, + "dependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "compression": "^1.8.0", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@types/compression": "^1.7.5", + "@types/express": "^5.0.0", + "@types/node": "^26.0.0", + "typescript": "^5.9.0" + } +} diff --git a/frameworks/nestjs/src/app.controller.ts b/frameworks/nestjs/src/app.controller.ts new file mode 100644 index 000000000..a9ad2c93d --- /dev/null +++ b/frameworks/nestjs/src/app.controller.ts @@ -0,0 +1,104 @@ +import { Controller, Get, Header, Param, Post, Query, Req } from '@nestjs/common'; +import { Request } from 'express'; +import { readFileSync } from 'node:fs'; + +interface Rating { + score: number; + count: number; +} + +interface DatasetItem { + id: number; + name: string; + category: string; + price: number; + quantity: number; + active: boolean; + tags: string[]; + rating: Rating; +} + +let dataset: DatasetItem[] = []; +try { + dataset = JSON.parse(readFileSync(process.env.DATASET_PATH || '/data/dataset.json', 'utf8')); +} catch { + dataset = []; +} + +function sumQuery(query: Record): number { + let sum = 0; + for (const value of Object.values(query)) { + const n = parseInt(String(value), 10); + if (!Number.isNaN(n)) sum += n; + } + return sum; +} + +// The default body parsers are disabled in main.ts, so the POST endpoints read +// the raw stream: they only sum or count what arrives. +function readBody(req: Request): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + req.on('data', (chunk: Buffer) => chunks.push(chunk)); + req.on('end', () => resolve(Buffer.concat(chunks))); + req.on('error', reject); + }); +} + +function countBody(req: Request): Promise { + return new Promise((resolve, reject) => { + let size = 0; + req.on('data', (chunk: Buffer) => { + size += chunk.length; + }); + req.on('end', () => resolve(size)); + req.on('error', reject); + }); +} + +@Controller() +export class AppController { + @Get('pipeline') + @Header('Content-Type', 'text/plain') + pipeline(): string { + return 'ok'; + } + + @Get('baseline11') + @Header('Content-Type', 'text/plain') + baselineGet(@Query() query: Record): string { + return String(sumQuery(query)); + } + + @Post('baseline11') + @Header('Content-Type', 'text/plain') + async baselinePost( + @Query() query: Record, + @Req() req: Request, + ): Promise { + let total = sumQuery(query); + const n = parseInt((await readBody(req)).toString().trim(), 10); + if (!Number.isNaN(n)) total += n; + return String(total); + } + + @Get('json/:count') + jsonItems(@Param('count') rawCount: string, @Query('m') rawM?: string) { + let count = parseInt(rawCount, 10) || 0; + if (count < 0) count = 0; + if (count > dataset.length) count = dataset.length; + const m = parseInt(rawM ?? '1', 10) || 1; + + const items = dataset.slice(0, count).map((item) => ({ + ...item, + total: item.price * item.quantity * m, + })); + return { items, count }; + } + + @Post('upload') + @Header('Content-Type', 'text/plain') + async upload(@Req() req: Request): Promise { + return String(await countBody(req)); + } +} diff --git a/frameworks/nestjs/src/app.module.ts b/frameworks/nestjs/src/app.module.ts new file mode 100644 index 000000000..848d4aaa7 --- /dev/null +++ b/frameworks/nestjs/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; + +@Module({ + controllers: [AppController], +}) +export class AppModule {} diff --git a/frameworks/nestjs/src/main.ts b/frameworks/nestjs/src/main.ts new file mode 100644 index 000000000..1be7556ac --- /dev/null +++ b/frameworks/nestjs/src/main.ts @@ -0,0 +1,35 @@ +import 'reflect-metadata'; +import { NestFactory } from '@nestjs/core'; +import cluster from 'node:cluster'; +import { readFileSync } from 'node:fs'; +import os from 'node:os'; +import compression from 'compression'; +import { AppModule } from './app.module'; + +function getCPUCount(): number { + try { + const max = readFileSync('/sys/fs/cgroup/cpu.max', 'utf8').trim(); + const [quota, period] = max.split(' '); + if (quota !== 'max') { + const cgroup = Math.floor(Number(quota) / Number(period)); + if (cgroup >= 1) return cgroup; + } + } catch { + // no cgroup limit, fall back to the host CPUs + } + return os.availableParallelism ? os.availableParallelism() : os.cpus().length; +} + +async function bootstrap() { + // bodyParser off: the POST endpoints read the raw stream themselves. + const app = await NestFactory.create(AppModule, { bodyParser: false, logger: false }); + app.use(compression()); + await app.listen(8080); +} + +if (cluster.isPrimary) { + const workers = getCPUCount(); + for (let i = 0; i < workers; i++) cluster.fork(); +} else { + bootstrap(); +} diff --git a/frameworks/nestjs/tsconfig.json b/frameworks/nestjs/tsconfig.json new file mode 100644 index 000000000..0d7efb486 --- /dev/null +++ b/frameworks/nestjs/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2023", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "outDir": "./dist" + }, + "include": ["src/**/*"] +} From 5ffff8dba78536159ae6275634488339b418f83c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 14 Aug 2026 06:36:04 +0000 Subject: [PATCH 2/2] Benchmark results: nestjs [skip ci] --- site/data/frameworks.json | 8 + site/data/results/nestjs.json | 243 ++++++++++++++++++ site/static/logs/baseline/4096/nestjs.log | 0 site/static/logs/baseline/512/nestjs.log | 0 site/static/logs/json-comp/16384/nestjs.log | 0 site/static/logs/json-comp/4096/nestjs.log | 0 site/static/logs/json-comp/512/nestjs.log | 0 site/static/logs/json/4096/nestjs.log | 0 site/static/logs/limited-conn/4096/nestjs.log | 0 site/static/logs/limited-conn/512/nestjs.log | 0 site/static/logs/pipelined/4096/nestjs.log | 0 site/static/logs/pipelined/512/nestjs.log | 0 site/static/logs/upload/256/nestjs.log | 0 site/static/logs/upload/32/nestjs.log | 0 14 files changed, 251 insertions(+) create mode 100644 site/data/results/nestjs.json create mode 100644 site/static/logs/baseline/4096/nestjs.log create mode 100644 site/static/logs/baseline/512/nestjs.log create mode 100644 site/static/logs/json-comp/16384/nestjs.log create mode 100644 site/static/logs/json-comp/4096/nestjs.log create mode 100644 site/static/logs/json-comp/512/nestjs.log create mode 100644 site/static/logs/json/4096/nestjs.log create mode 100644 site/static/logs/limited-conn/4096/nestjs.log create mode 100644 site/static/logs/limited-conn/512/nestjs.log create mode 100644 site/static/logs/pipelined/4096/nestjs.log create mode 100644 site/static/logs/pipelined/512/nestjs.log create mode 100644 site/static/logs/upload/256/nestjs.log create mode 100644 site/static/logs/upload/32/nestjs.log diff --git a/site/data/frameworks.json b/site/data/frameworks.json index 7f879a980..35d45fb1b 100644 --- a/site/data/frameworks.json +++ b/site/data/frameworks.json @@ -602,6 +602,14 @@ } ] }, + "nestjs": { + "dir": "nestjs", + "description": "NestJS 11 on the Express platform adapter, default configuration. Routing and parameter decorators through the Nest API, controller return values serialized by the framework, gzip through the compression middleware the Nest docs recommend.", + "repo": "https://github.com/nestjs/nest", + "type": "flagship", + "engine": "nodehttp", + "mode": "standard" + }, "ngx-php": { "dir": "ngx-php", "description": "Embedded PHP scripting language module for nginx.", diff --git a/site/data/results/nestjs.json b/site/data/results/nestjs.json new file mode 100644 index 000000000..7936502ef --- /dev/null +++ b/site/data/results/nestjs.json @@ -0,0 +1,243 @@ +{ + "framework": "nestjs", + "results": { + "baseline-4096": { + "framework": "nestjs", + "language": "TS", + "rps": 413334, + "avg_latency": "8.06ms", + "p99_latency": "22.10ms", + "cpu": "6414.6%", + "memory": "9.3GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "100.61MB/s", + "input_bw": "31.93MB/s", + "reconnects": 0, + "status_2xx": 2066673, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "baseline-512": { + "framework": "nestjs", + "language": "TS", + "rps": 430068, + "avg_latency": "1.19ms", + "p99_latency": "5.41ms", + "cpu": "6519.0%", + "memory": "8.6GiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "104.60MB/s", + "input_bw": "33.22MB/s", + "reconnects": 0, + "status_2xx": 2150340, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-4096": { + "framework": "nestjs", + "language": "TS", + "rps": 366074, + "avg_latency": "4.15ms", + "p99_latency": "31.10ms", + "cpu": "6354.0%", + "memory": "8.8GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "1.29GB/s", + "input_bw": "17.46MB/s", + "reconnects": 72871, + "status_2xx": 1830371, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-16384": { + "framework": "nestjs", + "language": "TS", + "rps": 52963, + "avg_latency": "91.18ms", + "p99_latency": "751.70ms", + "cpu": "6474.9%", + "memory": "8.8GiB", + "connections": 16384, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "77.07MB/s", + "input_bw": "3.94MB/s", + "reconnects": 8076, + "status_2xx": 264819, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-4096": { + "framework": "nestjs", + "language": "TS", + "rps": 50402, + "avg_latency": "62.38ms", + "p99_latency": "273.30ms", + "cpu": "6471.3%", + "memory": "8.0GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "73.37MB/s", + "input_bw": "3.75MB/s", + "reconnects": 8376, + "status_2xx": 252011, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "json-comp-512": { + "framework": "nestjs", + "language": "TS", + "rps": 60658, + "avg_latency": "8.44ms", + "p99_latency": "39.10ms", + "cpu": "6450.9%", + "memory": "7.8GiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "88.32MB/s", + "input_bw": "4.51MB/s", + "reconnects": 12045, + "status_2xx": 303290, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "limited-conn-4096": { + "framework": "nestjs", + "language": "TS", + "rps": 256857, + "avg_latency": "2.73ms", + "p99_latency": "21.40ms", + "cpu": "4495.7%", + "memory": "8.7GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "62.52MB/s", + "input_bw": "19.84MB/s", + "reconnects": 128426, + "status_2xx": 1284288, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "limited-conn-512": { + "framework": "nestjs", + "language": "TS", + "rps": 244377, + "avg_latency": "2.09ms", + "p99_latency": "21.10ms", + "cpu": "4466.9%", + "memory": "8.7GiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "59.49MB/s", + "input_bw": "18.88MB/s", + "reconnects": 122192, + "status_2xx": 1221885, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "pipelined-4096": { + "framework": "nestjs", + "language": "TS", + "rps": 880666, + "avg_latency": "59.89ms", + "p99_latency": "185.10ms", + "cpu": "6533.9%", + "memory": "9.4GiB", + "connections": 4096, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "211.57MB/s", + "reconnects": 0, + "status_2xx": 4403333, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "pipelined-512": { + "framework": "nestjs", + "language": "TS", + "rps": 884996, + "avg_latency": "9.27ms", + "p99_latency": "12.80ms", + "cpu": "6613.7%", + "memory": "9.3GiB", + "connections": 512, + "threads": 64, + "duration": "5s", + "pipeline": 16, + "bandwidth": "212.61MB/s", + "reconnects": 0, + "status_2xx": 4424981, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "upload-256": { + "framework": "nestjs", + "language": "TS", + "rps": 1855, + "avg_latency": "134.98ms", + "p99_latency": "646.90ms", + "cpu": "6531.2%", + "memory": "7.4GiB", + "connections": 256, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "474.79KB/s", + "input_bw": "14.71GB/s", + "reconnects": 1814, + "status_2xx": 9276, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + }, + "upload-32": { + "framework": "nestjs", + "language": "TS", + "rps": 1780, + "avg_latency": "17.95ms", + "p99_latency": "63.10ms", + "cpu": "5912.6%", + "memory": "6.7GiB", + "connections": 32, + "threads": 64, + "duration": "5s", + "pipeline": 1, + "bandwidth": "455.66KB/s", + "input_bw": "14.12GB/s", + "reconnects": 1784, + "status_2xx": 8900, + "status_3xx": 0, + "status_4xx": 0, + "status_5xx": 0 + } + } +} diff --git a/site/static/logs/baseline/4096/nestjs.log b/site/static/logs/baseline/4096/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/baseline/512/nestjs.log b/site/static/logs/baseline/512/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/16384/nestjs.log b/site/static/logs/json-comp/16384/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/4096/nestjs.log b/site/static/logs/json-comp/4096/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json-comp/512/nestjs.log b/site/static/logs/json-comp/512/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/json/4096/nestjs.log b/site/static/logs/json/4096/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/limited-conn/4096/nestjs.log b/site/static/logs/limited-conn/4096/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/limited-conn/512/nestjs.log b/site/static/logs/limited-conn/512/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/pipelined/4096/nestjs.log b/site/static/logs/pipelined/4096/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/pipelined/512/nestjs.log b/site/static/logs/pipelined/512/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/upload/256/nestjs.log b/site/static/logs/upload/256/nestjs.log new file mode 100644 index 000000000..e69de29bb diff --git a/site/static/logs/upload/32/nestjs.log b/site/static/logs/upload/32/nestjs.log new file mode 100644 index 000000000..e69de29bb