Skip to content
Merged
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
20 changes: 20 additions & 0 deletions frameworks/nestjs/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
26 changes: 26 additions & 0 deletions frameworks/nestjs/README.md
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions frameworks/nestjs/meta.json
Original file line number Diff line number Diff line change
@@ -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": []
}
18 changes: 18 additions & 0 deletions frameworks/nestjs/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
104 changes: 104 additions & 0 deletions frameworks/nestjs/src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>): 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<Buffer> {
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<number> {
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, unknown>): string {
return String(sumQuery(query));
}

@Post('baseline11')
@Header('Content-Type', 'text/plain')
async baselinePost(
@Query() query: Record<string, unknown>,
@Req() req: Request,
): Promise<string> {
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<string> {
return String(await countBody(req));
}
}
7 changes: 7 additions & 0 deletions frameworks/nestjs/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';

@Module({
controllers: [AppController],
})
export class AppModule {}
35 changes: 35 additions & 0 deletions frameworks/nestjs/src/main.ts
Original file line number Diff line number Diff line change
@@ -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();
}
14 changes: 14 additions & 0 deletions frameworks/nestjs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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/**/*"]
}
8 changes: 8 additions & 0 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Loading