npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@noego/dinner

v0.0.7

Published

Dinner turns your OpenAPI file into a running Express app. You describe your API once (paths, params, schemas) and Dinner wires up routing, validation, and middleware to your controllers — so you write business logic, not glue code.

Readme

@noego/dinner

Dinner turns your OpenAPI file into a running Express app. You describe your API once (paths, params, schemas) and Dinner wires up routing, validation, and middleware to your controllers — so you write business logic, not glue code.

npm version License: ISC

Why Dinner?

  • OpenAPI as the source of truth: one spec drives routes, validation, and docs.
  • Zero‑boilerplate routing: easily map paths to your controller methods.
  • Built‑in validation using JSON Schema directly from your OpenAPI spec.
  • Composable middleware system, compose and order them, and inherit at module/global levels.
  • Module‑first URL design: organize endpoints in modules with basePath for clean, predictable URLs across large APIs.
  • Built on Express: battle‑tested with a rich ecosystem and familiar middleware. Faster routing via static‑prefix bucketing and rich URL parameter design.
  • Dev‑friendly: quick iteration with watch mode; minimal framework magic.
  • Forking of server for better CPU usage.

Installation

# Install the framework and its peer dependency
npm install @noego/dinner express
# TypeScript (recommended)
npm install -D typescript ts-node @types/node @types/express

Requires Node.js 18+.

Peer dependency: Express

Dinner is built on Express and declares express as a peer dependency. This lets your app control the exact Express version and configuration you use. Make sure you install a compatible Express version (v5 recommended) in your application:

npm install express@^5

Quick Start

Design your routing logic using OpenAPI spec format.

  1. Create an OpenAPI file:
# openapi.yaml
openapi: 3.0.0
info:
  title: My API
  version: 1.0.0
paths:
  /hello:
    get:
      x-controller: hello.controller
      x-action: hello
      responses:
        '200':
          description: OK
  1. Create a controller:
// controllers/hello.controller.ts

/**
 * Ensure the controller is the default export from the module. 
 */

export default class HelloController {
  hello() {
    return { message: 'Hello, world!' };
  }
}
  1. Bootstrap Express with Dinner:
// server.ts
import path from 'path';
import express from 'express';
import { Server } from '@noego/dinner';

async function start() {
  const app = express();

  /**
   * This is a basic express application. Add express.json for json support.
   */
  app.use(express.json());

  await Server.createServer({
    openapi_path: path.join(__dirname, 'openapi.yaml'), //path to openapi file
    controllers_base_path: path.join(__dirname, 'controllers'), //path to all controller files
    server: app, //express server

    /**
     * Your default export from your controller class is passed to the controller builder.
     * You can design how you instanciate your controller. We suggest using an IOC library
     * to instanciate your controller.
     */
    controller_builder: async (Controller) => new Controller(),

    /**
     * The req, res are passed to this function. This allows you to customize what is passed to your controller
     * You can add any global settings inside of here. These will be modified before each controller 
     * receives a request.
     */
    controller_args_provider: async (req, res) => ({ req, res })
  });

  /**
   * Listen to your app as a normal express application. Feel free to add whatever you need on top.
   */
  app.listen(3000, () => console.log('http://localhost:3000'));
}

start();
  1. Run in dev:
npx ts-node server.ts

Core Concepts

Core extensions

  • x-controller: controller module to load (e.g., hello.controllercontrollers/hello.controller.ts)
  • x-action: method to invoke on the controller instance (e.g., hello)
  • x-middleware: middleware identifiers to apply (resolved under middleware_path)

Parameters (OpenAPI style)

Use {id} in your path to declare a path parameter. To constrain what matches, add a regex in the path itself — this is what the router uses for parsing/validation at match time (OpenAPI schemas are still useful for documentation and body/query validation).

paths:
  /users/{id}: # This will accept anything
    get:
      x-controller: controllers/user.controller
      x-action: get
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }

paths:
  # Bracket Notation
  /users/{id:\\d+}: # This will only accept numbers
    get:
      x-controller: controllers/user.controller
      x-action: get
  # Colon Notation
  /users/:id(\\d+): # This is the same filter just different syntax
    get:
      x-controller: controllers/user.controller
      x-action: get

Request body and responses

Define requestBody.content['application/json'].schema and responses per OpenAPI. Dinner compiles JSON Schemas and validates requests before calling your controller. The schema you add to the endpoint will be validated before it every reaches your controller. This will prevent unvalidated request from reaching your application. This also stops you from having to do validation in your application. The only validation required after this will be business logic.

paths:
  "/user/create":
    post:
      x-controller: controllers/post.controller
      x-action: create
      summary: Create a new Post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                content: 
                  type: string

You can use $ref to better define and separate your application validation. This provides a cleaner definitions inside your route definitions.

paths:
  "/user/create":
    post:
      x-controller: controllers/post.controller
      x-action: create
      summary: Create a new Post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/NewPost"

components:
  schemas:
    NewPost:
      type: object
      properties:
        title:
          type: string
        content:
          type: string
      required:
        - title
        - content

Global and module base paths

  • A root-level basePath prefixes all paths.
  • Within a module, its basePath prefixes the module’s paths.
  • The parser combines them: /<basePath>/<module.basePath>/<module.paths…>.

Root Example

basePath: /api
paths:
  /ping:
    get:
      x-controller: controllers/ping.controller
      x-action: ping

This will create /api/ping as a route.

Module Example

  • Instead of repeating prefixes, declare a module section. The parser (framework/openapi/parser) flattens module paths into the top-level paths map.
module:
  users:
    basePath: /users
    paths:
      '/':
        get:
          x-controller: controllers/user.controller
          x-action: getAll
      '/{id}':
        get:
          x-controller: controllers/user.controller
          x-action: get

This will expand to /users/ and /users/{id} allowing you to group modules under the same base url.

Middleware

Adding middleware allows you to do additional validation prior to a request reaching your controller. You can define your middleware path inside of the ServerOptions

// server.ts
import path from 'path';
import express from 'express';
import { Server } from '@noego/dinner';

async function start() {
  const app = express();

  /**
   * This is a basic express application. Add express.json for json support.
   */
  app.use(express.json());

  await Server.createServer({
    openapi_path: ...,
    controllers_base_path: ...,
    server: app,
    controller_builder: ...,
    controller_args_provider: ...,
    /**
     * Adding a middleware path allows you to add additional 
     * middleware definitions to your application
     */
    middleware_path: path.join(__dirname, "middleware"),
  });
  app.listen(3000, () => console.log('http://localhost:3000'));
}

start();
// middleware/core/auth.ts

/**
 * These are familiar express middleware that is just exported from a module.
 */
export default function UserAuth(req:any,reply:any,next:any){

  // Auth Logic
  next();
}


export function validate_session(req,res,next){

  // Logic
  next()
}

Defining middleware names in spec

There are a couple of syntax to define middleware. Having this flexibility gives the user a greater amount of ways to group middleware functions.

module:
  users:
    basePath: /users
    paths:
      '/':
        get:
          x-controller: controllers/user.controller
          x-action: getAll
          x-middleware:
            - core.auth  # This will load middleware/core/auth.ts and load the default function
            - core.auth:validate_session # This will load middleware/core/auth.ts and load the validate_session function if it's exported.
            - core.auth:* # This will load all the exported functions from the middleware (no order gauranteed)
            - core.auth:validate_session,default # This will load the specified functions and run in the order that they are listed

Middleware inheritance

  • If you specify module.users.x-middleware: [...], the parser applies those entries to each route within the module (in addition to the route’s own x-middleware).

URL transformation and matching

  • The parser normalizes your URL templates for routing. It supports both OpenAPI-style {id} parameters and advanced path patterns (see “Route Path Patterns”).
  • Matching is done from the start to the end of the path; query strings are ignored for matching.
  • The router pre-indexes routes by static prefixes for performance and uses path-to-regex to verify and extract params.
/user/{id}:
  get:
    x-controller: controllers/user.controller
    x-action: get
    x-middleware:
      - user.auth
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
    responses:
      '200': { description: Success }

Modules

Group related endpoints with a module block and a basePath to avoid repetition:

module:
  post:
    basePath: '/posts'
    paths:
      '/':
        get:
          x-controller: controllers/post.controller
          x-action: getAll
      '/create':
        post:
          x-controller: controllers/post.controller
          x-action: create
      '/{id}':
        get:
          x-controller: controllers/post.controller
          x-action: get

Route Path Patterns (path-to-regex)

Dinner supports advanced URL matching using path-to-regex patterns in your path strings. Use these to express optional parts, wildcards, and custom regex captures.

Key patterns

  • :key – named segment (single path part)
  • :key? – optional named segment
  • :key* – zero or more segments (split by /), returns an array
  • :key(…) – custom regex for the segment (e.g., (\d+), (.*))
  • Repeated names – :key … :key returns arrays (multiple captures)

Notes

  • Matching is case-sensitive by default.
  • By default, matching is from the start and to the end of the URL path.
  • The separator is /.

Examples

Single and multiple params

paths:
  /user/:id:
    get:
      x-controller: controllers/user.controller
      x-action: get
  /user/:foo/:bar:
    get:
      x-controller: controllers/user.controller
      x-action: pair

Optional segments

paths:
  /foo/:bar?:
    get:
      x-controller: controllers/foo.controller
      x-action: maybeBar

Numeric constraint

paths:
  /foo/:bar(\d+):
    get:
      x-controller: controllers/foo.controller
      x-action: onlyNumbers

Wildcard segments vs catch-all

paths:
  # :path* captures multiple segments, split by '/'
  /assets/:path*:
    get:
      x-controller: controllers/assets.controller
      x-action: serve

  # (.*) captures everything, including '/'
  /report/:slug(.*)/download:
    get:
      x-controller: controllers/report.controller
      x-action: download

Repeated names (arrays)

paths:
  /foo/:bar/:bar:
    get:
      x-controller: controllers/foo.controller
      x-action: repeated

Simple and advanced params

  • For simple path params, use either {id} (OpenAPI style) or :id (colon style) — both are supported.
  • For advanced matching (regex and modifiers), you can use either style. The URL transformer converts {name[:regex][modifier]} into the equivalent colon form for matching.
paths:
  # Colon forms
  /order/:id(\\d+): { get: { x-controller: controllers/order.controller, x-action: get } }
  /files/:path*:   { get: { x-controller: controllers/files.controller, x-action: list } }

  # Brace forms (equivalent)
  /order/{id:\\d+}: { get: { x-controller: controllers/order.controller, x-action: get } }
  /files/{path:(.*)}:   { get: { x-controller: controllers/files.controller, x-action: list } }

Path patterns: updated guidance

Dinner transforms brace parameters to colon form before compiling with path-to-regex. Use these forms for reliable matching:

  • Brace grammar: {name[:pattern][modifier]} with modifier (?, *, +) only when no pattern is present.
  • Multi‑segment capture: use (.*) (e.g., {slug:(.*)}:slug(.*)).
  • Non‑greedy capture: use .*? (e.g., {a:.*?}-x/{b:.*}:a(.*?)-x/:b(.*)).
  • Literal text in patterns must escape regex characters (e.g., {ext:\\.tar\\.gz}).
  • Note: in this package, :name* and :name+ are not multi‑segment globs — prefer explicit (.*).

Examples

paths:
  /order/{id:\\d+}:            { get: { x-controller: controllers.order,  x-action: get } }
  /report/{slug:(.*)}/download:  { get: { x-controller: controllers.report, x-action: download } }
  /a/{foo?}:                     { get: { x-controller: controllers.a,      x-action: get } }
  /file/{ext:\\.tar\\.gz}:       { get: { x-controller: controllers.file,   x-action: get } }

You can also set a global basePath to prefix everything:

basePath: '/api'

Controllers

Dinner loads controllers dynamically based on x-controller and calls the method in x-action.

Location

x-controller is resolved relative to controllers_base_path. Omit the file extension.

# openapi.yaml
paths:
  /users/{id}:
    get:
      x-controller: controllers/user.controller
      x-action: get
Resolved file:
<controllers_base_path>/controllers/user.controller.ts
# Omit the file extension in x-controller

Export

Controllers must be the default export of the module.

// src/controllers/user.controller.ts
export default class UserController {
  async get({ params }: { params: { id: string } }) {
    return { id: params.id };
  }
}

Instantiation

Use controller_builder to create controller instances — plug in DI or plain constructors. Dinner passes you the raw controller class (constructor). You decide how to instantiate it: directly with new, through a DI container, as a singleton, or with per‑request state.

// server setup
await Server.createServer({
  // ...
  controller_builder: async (Controller) => new Controller(),
});

DI container example

import createContainer from '@noego/ioc';
const container = createContainer();

await Server.createServer({
  // ...
  controller_builder: async (Controller) => {
    // Resolve via your container
    return container.instance(Controller);
  },
});

Singleton (per class) example

const singletons = new WeakMap<Function, any>();

await Server.createServer({
  // ...
  controller_builder: async (Controller) => {
    if (!singletons.has(Controller)) {
      singletons.set(Controller, new Controller());
    }
    return singletons.get(Controller);
  },
});

Factory using context

await Server.createServer({
  // ...
  controller_builder: async (Controller, context) => new Controller(context),
  context_builder: async (req) => ({ requestId: req.headers['x-request-id'] }),
});

Arguments

Use controller_args_provider to shape the single argument passed to your controller action.

// server setup
await Server.createServer({
  // ...
  controller_args_provider: async (req, res, context) => ({
    req,
    res,
    context,
    body: req.body,
    params: req.params,
    query: req.query,
  }),
});

Context

Optionally provide a context_builder to compute per‑request context (e.g., requestId, auth info) before controller_args_provider runs.

// server setup
await Server.createServer({
  // ...
  context_builder: async (req, res) => ({
    requestId: String(req.headers['x-request-id'] || ''),
    user: (req as any).user || null,
  }),
});

Recommended minimal setup

import express from 'express';
import { Server } from '@noego/dinner';

await Server.createServer({
  openapi_path: 'openapi.yaml',
  controllers_base_path: 'src',
  server: express(),
  controller_builder: async (Controller) => new Controller(),
  controller_args_provider: async (req, res, context) => ({
    req,
    res,
    context,
    body: req.body,
    params: req.params,
    query: req.query,
  }),
  context_builder: async (req, res) => ({ requestId: req.headers['x-request-id'] }),
});

Controller example

// src/controllers/user.controller.ts
export default class UserController {
  async get({ params }: { params: { id: string } }) {
    return { id: params.id };
  }
}

Validation & Params

Dinner compiles JSON Schemas from your OpenAPI and validates requests with Ajv before your controller action runs. Invalid requests get a 400 response.

Define parameters (path, query), request bodies, and responses in YAML:

paths:
  /users/{id}:
    get:
      x-controller: controllers/user.controller
      x-action: get
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
        - name: include
          in: query
          required: false
          schema:
            type: string
            enum: [profile, posts]
      responses:
        '200':
          description: OK
  /users:
    post:
      x-controller: controllers/user.controller
      x-action: create
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewUser'
      responses:
        '201': { description: Created }

components:
  schemas:
    NewUser:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8

Provider mapping

  • Path params → req.params by name (e.g., { id: '123' }).
  • Query params → req.query (strings unless you coerce types yourself).
  • Body → req.body (JSON content-type recommended).

Ajv formats

  • Enable built‑in formats (e.g., email, uri) by setting ajv_formats:
await Server.createServer({
  // ...
  ajv_formats: true,            // all default formats
  // or choose specific ones
  // ajv_formats: ['email', 'uri']
});

Error handling

  • If the request body doesn’t match the schema, Dinner returns 400 before your action runs.
  • For path/query params, define schemas as shown; Dinner uses them to build route validation.

Split Your Spec (with @noego/stitch)

Use a small "stitch config" to merge module files before Dinner processes them. The stitch file can list modules as an array or object.

stitch.yaml

stitch:
  - ./spec/users.yaml
  - ./spec/posts.yaml

spec/users.yaml

openapi: 3.0.0
module:
  users:
    basePath: /users
    paths:
      '/':
        get:
          x-controller: controllers/user.controller
          x-action: getAll
      '/{id}':
        get:
          x-controller: controllers/user.controller
          x-action: get

Then point Dinner at the stitch file instead of a raw OpenAPI file:

await Server.createServer({
  openapi_path: path.join(__dirname, 'stitch.yaml'),
  controllers_base_path: path.join(__dirname, 'controllers'),
  server: app,
  controller_builder: async (C) => new C(),
  controller_args_provider: async (req, res) => ({ req, res }),
});

Dinner detects a stitch document, uses @noego/stitch to merge your YAML files, resolves $refs, and then builds routes. Note: Stitch itself only merges/validates; any basePath handling happens in your OpenAPI content (and Dinner’s processing), not in stitch.yaml.

Stitch: Detailed Examples

Project structure example

project/
├── stitch.yaml
├── spec/
│   ├── users.yaml
│   ├── posts.yaml
│   └── components/
│       └── schemas.yaml
└── controllers/
    ├── user.controller.ts
    └── post.controller.ts

Sample stitch.yaml

stitch:
  - ./spec/base.yaml
  - ./spec/paths.yaml

Glob patterns

Stitch can expand globs in stitch.yaml. Globs are resolved relative to the stitch.yaml directory, directories are ignored, and matches are sorted alphabetically for deterministic merging.

stitch:
  - ./spec/base.yaml
  - ./spec/paths/*.yaml        # include all path fragments
  - ./spec/components/*.yaml   # include component fragments

VSCode YAML schema integration

npx stitch install schema.json --target "**/*.yaml"

Middleware Patterns

Middleware entries use a compact syntax to reference files and exported functions.

  • Dot notation → file path: folder.file resolves to folder/file.ts under middleware_path (or controllers_base_path fallback).
  • Default export: auth loads the default export from middleware/auth.ts.
  • Named exports: auth:cookie,bearer loads only the named exports cookie and bearer (in order).
  • All exports: auth:* loads all exported functions from the module.
  • Explicit default: auth:default is equivalent to auth.

Resolution rules

  • Base dir is middleware_path if provided. Otherwise Dinner falls back to controllers_base_path, then process.cwd().
  • Dots are converted to slashes: security.auth.sessionsecurity/auth/session.ts.
  • Errors are thrown for missing modules/functions or non-function exports.
paths:
  /user/{id}:
    get:
      x-middleware:
        - auth                  # default export from middleware/auth.ts
        - auth:cookie,bearer    # only these named exports (in order)
        - auth:*                # all exported functions
        - security.auth.session # dot notation → security/auth/session.ts

Example middleware file:

// middleware/auth.ts
export default function authenticate(req, res, next) { next(); }
export function cookie(req, res, next) { next(); }
export function bearer(req, res, next) { next(); }

Execution order

  • Middleware run sequentially in the order listed in x-middleware.
  • For comma-separated names (name1,name2), functions run in that order.

Controllers & DI

Controllers are plain classes. Use dependency injection with [@noego/ioc] if you prefer:

import { Component, Inject } from '@noego/ioc';
import { UserService } from '../services/userService';

@Component()
export default class UserController {
  constructor(@Inject(UserService) private userService: UserService) {}

  async getAll() {
    const users = await this.userService.getUsers();
    return { users, total: users.length };
  }
}

Provide your own controller lifecycle via controller_builder and request args via controller_args_provider:

const controller_builder = async (Controller) => new Controller();
const controller_args_provider = async (req, res) => ({ req, res });

Configuration

type ServerOptions = {
  openapi_path: string;                // absolute path to OpenAPI file
  controllers_base_path: string;       // absolute base path for controllers
  middleware_path?: string;            // absolute base path for middleware
  controller_builder?: (Controller, ctx?) => Promise<any>;
  controller_args_provider?: (req, res, ctx?) => Promise<any>;
  context_builder?: (req, res) => Promise<any>;
  server?: import('express').Application; // existing Express app
  ajv_formats?: string[] | true;       // configure AJV formats
}
``;

Notes:
- Dinner dynamically imports controllers; ensure your build/runtime can resolve your controller modules by path.

## TypeScript

Recommended minimal `tsconfig.json` settings:

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "outDir": "dist",
    "strict": true
  },
  "include": ["./**/*.ts"]
}

Examples & Docs

  • Examples: see the example/ folder for controllers, middleware, and OpenAPI usage
  • Middleware guide: docs/middleware.md
  • Advanced validation: docs/advanced_validation.md

Benefits:

  • Development: Automatic restart on file changes for faster iteration
  • Production: Main process manages workers for improved reliability
  • Scalability: Can be extended to support multiple workers for load balancing

Performance Benchmarks

  • What it does: Exercises in-memory HTTP calls (Supertest) against two apps: one built with Dinner, one with plain Express. Measures requests/sec and average latency. No ports are opened.
  • Where it lives: scripts/benchmark.js

Run a quick benchmark

npm run bench

Tune what’s measured (env vars)

  • BENCH_ROUTESETS: CSV of route counts to test (default: 10,100,500,1000)
  • BENCH_ROUTES: Single route count (overrides BENCH_ROUTESETS)
  • BENCH_SUBROUTES: Subroutes per base route (default: 5)
  • BENCH_REPEATS: Passes through all URLs per iteration (default: 2)
  • BENCH_ITERS: Iterations per size for mean±sd (default: 5)
  • BENCH_ENGINES: Which engines to run, comma-separated (dinner,express by default). Examples: dinner or express.
  • BENCH_REQ_TIMEOUT_MS: Per-request timeout (default: 60000, 0 to disable)
  • BENCH_ITER_TIMEOUT_MS: Per-iteration timeout (default: 120000)
  • BENCH_PROGRESS_EVERY: Print progress every N requests (default: off)
  • BENCH_PAUSE_MS: Sleep between iterations (default: 0)

GC and stability

  • The bench script runs Node with --expose-gc and triggers global.gc() between warmups and iterations to reduce memory pressure variability. Set BENCH_LOG_GC=1 to log GC checkpoints.

Examples

# Default multi-size run (10,100,500,1000 routes)
npm run bench

# Single size, more iterations
BENCH_ROUTES=500 BENCH_ITERS=10 npm run bench

# Heavier URL matrix
BENCH_ROUTESETS=100,1000 BENCH_SUBROUTES=10 BENCH_REPEATS=3 npm run bench

# Inspect GC pauses (verbose)
npm run bench:gc
# or with NODE_OPTIONS
NODE_OPTIONS="--expose-gc --trace-gc --trace-gc-ignore-scavenger" npm run bench

Profiling

# Generate a flamegraph with Clinic (installs Clinic if needed)
npm run profile
# Output is written under .clinic/*.html — open in your browser

# Analyze async/await flow (Bubbleprof)
npm run profile:async
# Output under .clinic/*.html — look for slow async edges, long timers, or blocked chains

Outputs

  • Console tables: per-size summary showing Req/s (mean±sd) and Avg ms/req (mean±sd) plus speedup.
  • Markdown block: ready-to-paste tables for the README (includes environment and config).

Tip

  • Ensure a build exists before benchmarking. The script runs npm run build automatically via the bench npm script.

Testing

Integration Testing

Test your API with Supertest:

import express, { Application } from "express";
import request from "supertest";
import path from "path";
import { Server, ServerOptions } from "@noego/dinner";
import createContainer from "@noego/ioc";
import { ModuleSetup, build_loader_function, build_provider_function } from "./setup/module_setup";

describe('API Tests', () => {
  let app: Application;

  beforeAll(async () => {
    const container = createContainer();
    ModuleSetup(container);

    const appInstance = express();
    appInstance.use(express.json());

    const options: ServerOptions = {
      openapi_path: path.resolve(__dirname, "./openapi.yaml"),
      controllers_base_path: path.resolve(__dirname),
      loader_function: build_loader_function(container),
      provider_function: build_provider_function(container),
      server: appInstance
    };

    const server = await Server.createServer(options);
    app = server.implementation.server as Application;
  });

  it('should return hello world', async () => {
    const response = await request(app).get('/hello');

    expect(response.status).toBe(200);
    expect(response.body).toEqual({
      message: "Successful request. Hello World!",
      error: false
    });
  });
});

Service Pattern

Use the dependency injection to create and use services in your controllers:

// services/example.service.ts
import { Component } from "@noego/ioc";

@Component()
export class ExampleService {
  async performAction(data: any) {
    // Service implementation
    return { result: true, data };
  }
}
// controllers/example.controller.ts
import { Component, Inject } from "@noego/ioc";
import { ExampleService } from "../services/example.service";

@Component()
export default class ExampleController {
  constructor(@Inject(ExampleService) private service: ExampleService) {}
  
  async doSomething({ req, reply }) {
    const result = await this.service.performAction(req.body);
    return result;
  }
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

ISC License — see the LICENSE file for details.