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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@silay/core

v1.0.3

Published

Silay is a **mini-framework for Node.js**, built on top of Express, that gives your application a defined shape: a **dependency injection container**, a **module registration system**, and a **declarative router**, instead of leaving you to hand-wire `app

Readme

✨ What is Silay?

Silay is a mini-framework for Node.js, built on top of Express, that gives your application a defined shape: a dependency injection container, a module registration system, and a declarative router, instead of leaving you to hand-wire app.use() and app.get() calls across a growing codebase.

Rather than replacing Express, Silay uses it as its HTTP layer and adds the structural pieces most Express apps eventually need to build by hand:

  • A DI container so services, controllers, and middleware are registered once and resolved wherever they're needed.
  • A module manager that registers and instantiates classes, functions, and values in a predictable, dependency-aware order.
  • A declarative router that maps route definitions to controller methods, instead of scattering handlers across files.
  • A small set of built-in middleware (request tracking, a standardized response helper) applied automatically to every app.

Who it's for: developers who like Express's simplicity but want a consistent way to organize controllers, services, and routes as an application grows past a handful of endpoints — without adopting a large, opinionated framework.

How it differs from plain Express: a bare Express app leaves dependency wiring, controller organization, and route/middleware composition entirely up to you. Silay layers a container and a builder API (AppBuilder) on top of Express so those concerns follow one consistent pattern across the whole app.


🚀 Features

  • 🛣️ Routing — declarative route definitions mapped to controller methods, with per-route middleware.
  • 💉 Dependency Injection — a container supporting classes, functions, and values, with singleton resolution and dependency graphs.
  • 🧩 Modular Architecture — register controllers, services, middleware, and schemas as module items and let Silay instantiate them in the right order.
  • 🔌 Middleware — global (app-level) and route-level middleware, resolved through the DI container.
  • Lightweight Core — a thin layer over Express; no hidden runtime, no code generation.
  • 🏗️ Built-in Request Utilities — automatic request tracking headers and a res.sendResponse() helper on every request.
  • 🧱 Extensible Error Handling — a default JSON error handler, or supply your own via the container.

📦 Installation

npm install @silay/core

Silay depends on Express 5, so it requires Node.js 18 or later.


⚡ Quick Start

const AppBuilder = require('@silay/core');

// 1. A controller — a plain class with methods that handle requests
class HelloController {
  sayHello(req, res) {
    res.sendResponse(200, { message: 'Hello from Silay!' });
  }
}

// 2. Create the app builder
const app = new AppBuilder();

// 3. Register the controller with the DI container / module system
app.registerModuleConfigs([
  {
    key: 'helloController',
    type: 'controller',
    Class: HelloController,
    dependencies: [],
  },
]);

// 4. Declare routes for that controller
const helloRoutes = {
  configs: {
    prefix: '/hello',
    controller: 'helloController',
  },
  routes: [
    { url: '/', httpMethod: 'get', methodName: 'sayHello' },
  ],
};

// 5. Build the routes and start the server
app.buildRoutes([helloRoutes]);
app.listen(3000, () => console.log('Silay app running on port 3000'));

GET /hello/ now responds with:

{ "message": "Hello from Silay!", "trackingHeaders": { "userIp": "...", "userAgent": "...", "requestId": "..." } }

🛣️ Routing

Routing in Silay is declarative: you describe a group of routes and the controller that serves them, and RouterManager wires them into an Express Router() under the hood.

A route module has two parts — configs (the controller and an optional URL prefix) and routes (the individual endpoints):

const userRoutes = {
  configs: {
    prefix: '/users',
    controller: 'userController', // key registered in the DI container
  },
  routes: [
    { url: '/', httpMethod: 'get', methodName: 'list' },
    { url: '/:id', httpMethod: 'get', methodName: 'getById' },
    { url: '/', httpMethod: 'post', methodName: 'create' },
    { url: '/:id', httpMethod: 'put', methodName: 'update' },
    { url: '/:id', httpMethod: 'patch', methodName: 'partialUpdate' },
    { url: '/:id', httpMethod: 'delete', methodName: 'remove' },
  ],
};

app.buildRoutes([userRoutes]);
  • Supported HTTP methods — any method available on an Express Router (get, post, put, patch, delete, etc.); httpMethod defaults to get if omitted.
  • Route parameters — standard Express path parameters (/:id) are supported, since routes are registered directly on an Express router.
  • Handler structuremethodName must match a method on the controller instance registered under controller. Silay resolves the controller from the DI container and calls controller[methodName](req, res, next), automatically forwarding thrown/rejected errors to next().
  • Route-level middleware — each route can list middleware by name (see Middleware); they run before the handler, in order.
  • Multiple route modules can be passed to buildRoutes([...]) to compose a full application from separately defined route groups.

💉 Dependency Injection

Silay's DI container (DIContainer) resolves services, controllers, and middleware by name instead of requiring you to require() and wire them together manually.

Registering dependencies

The container exposes three registration methods, each accepting an optional { singleton, type }:

const container = app.getContainer();

container.registerClass('logger', Logger, { singleton: true, type: 'service' });
container.registerFunction('formatDate', (date) => date.toISOString(), { singleton: true });
container.registerValue('config', { port: 3000 }, { singleton: true });
  • registerClass(name, Class, options) — registers a constructor. Instances of classes are instantiated through instantiateClass() (or automatically by the module system — see Modules), not get().
  • registerFunction(name, fn, options) — registers a plain function as a resolvable dependency.
  • registerValue(name, value, options) — registers a ready-made value or instance.
  • Singletons default to true: once resolved, the same instance/value is returned on every subsequent get().

Resolving dependencies

container.has('logger');           // -> true
container.get('formatDate');       // -> the registered function/value
container.instantiateClass('logger', deps); // -> a class instance
container.getAllByType('service'); // -> { logger: <instance>, ... }

In practice, most apps don't call the container directly — they register a module item with dependencies: [...], and the module system resolves and injects those dependencies automatically when it instantiates the class:

class DatabaseService {
  constructor() { /* ... */ }
}

class UserService {
  constructor(databaseService) {
    this.db = databaseService;
  }
}

app.registerModuleConfigs([
  { key: 'databaseService', type: 'service', Class: DatabaseService, dependencies: [] },
  { key: 'userService', type: 'service', Class: UserService, dependencies: ['databaseService'] },
]);

Here, UserService receives an already-instantiated DatabaseService as its constructor argument, resolved from the container by key.


🧩 Modules

A module item is a plain object describing something that should be registered in the DI container: a controller, service, middleware, or schema. ModuleManager processes a list of these items in two phases — first registering every definition, then instantiating singletons (resolving each item's dependencies along the way).

app.registerModuleConfigs([
  // A service with no dependencies
  { key: 'databaseService', type: 'service', Class: DatabaseService, dependencies: [] },

  // A controller that depends on the service above
  {
    key: 'userController',
    type: 'controller',
    Class: UserController,
    dependencies: ['databaseService'],
  },

  // A reusable value, e.g. a validation schema
  { key: 'userSchema', type: 'schema', schema: userSchemaObject },
]);

Each item supports:

| Field | Description | | -------------- | ----------------------------------------------------------------------------| | key | Unique name used to register and resolve the item in the container. | | type | Category of the item (controller, service, middleware, schema, etc. middleware and schema are handled specially — see below). | | Class / fn / schema | The implementation — a constructor (Class), a function (fn, for middleware), or a value (schema). | | dependencies | Array of other module keys to resolve and inject, in declaration order. | | options.singleton | Whether the instance is cached and reused (defaults to true). |

Calling app.registerModuleConfigs([...]) both registers the items and immediately loads them (registerItems + loadModules), so controllers and services are ready before routes are built. Because resolution happens by key, modules can be organized and registered from separate files and combined into a single array — this is how larger applications are expected to be composed: group related services/controllers/routes per feature, then pass each group's module items and route definitions into the builder.


🔌 Middleware

Silay applies a small set of built-in middleware to every app automatically (via createApp()): static file serving from /public, JSON and URL-encoded body parsing, cookie parsing, request tracking, and the response helper. On top of that, you can register your own middleware at the global or route level.

Built-in middleware

  • trackingHeaders — attaches req.trackingHeaders (userIp, userAgent, requestId) to every request.
  • sendResponse — adds res.sendResponse(statusCode, data), which sends a JSON response merged with the current request's tracking headers.

Global middleware

// A plain function
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// Or a middleware registered through the module system, referenced by key
app.registerModuleConfigs([
  { key: 'requestLogger', type: 'middleware', fn: (req, res, next) => {
      console.log(`${req.method} ${req.url}`);
      next();
    },
  },
]);

app.use('requestLogger');

Route-level middleware

Reference registered middleware by name on individual routes:

const routes = {
  configs: { prefix: '/users', controller: 'userController' },
  routes: [
    {
      url: '/:id',
      httpMethod: 'get',
      methodName: 'getById',
      middlewares: [{ name: 'requestLogger' }],
    },
  ],
};

Route middleware runs in the order listed, before the controller's handler.

Error handling

If no custom error handler is supplied, Silay attaches a default one that logs the error and responds with a JSON error payload (including the stack trace outside of NODE_ENV=production). You can override it by registering your own handler in the container and calling withErrorHandler(name):

container.registerFunction('errorHandler', () => (err, req, res, next) => {
  res.status(err.status || 500).json({ message: err.message });
});

app.withErrorHandler('errorHandler');

🏗️ Architecture

AppBuilder is the entry point that ties everything together: it owns the DI container, the module manager, and the router manager, and produces the underlying Express app.

graph TD
    AppBuilder --> DIContainer
    AppBuilder --> ModuleManager
    AppBuilder --> RouterManager
    AppBuilder --> ExpressApp["Express App (createApp)"]

    ModuleManager -->|registers controllers, services, middleware| DIContainer
    RouterManager -->|resolves controllers & middleware| DIContainer
    RouterManager -->|mounts Router| ExpressApp

    ExpressApp --> BuiltIns["Built-in middleware: static, json, urlencoded, cookies, tracking, sendResponse"]
    BuiltIns --> Request
    RouterManager --> Request

Component responsibilities:

  • AppBuilder — the public API. Registers modules, applies global middleware, builds routes, and starts the server (listen) or exposes the raw Express instance (getApp).
  • DIContainer — a registry of classes, functions, and values, resolved by key, with singleton caching.
  • ModuleManager — turns module item definitions into container registrations, resolving each item's declared dependencies before instantiating it.
  • RouterManager — translates route definitions into an Express Router, resolving controllers and middleware from the container for each route.
  • createApp (app-factory) — builds the base Express app and applies the built-in middleware stack.

🎯 Design Philosophy

  • Simplicity — Silay is a thin layer over Express, not a replacement runtime; there's no compiler, code generation, or hidden magic.
  • Explicit registration — every controller, service, and middleware is explicitly registered with a key and its dependencies; nothing is auto-discovered from the filesystem.
  • Separation of concerns — routing (RouterManager), dependency resolution (DIContainer), and lifecycle wiring (ModuleManager) are distinct components with focused responsibilities.
  • Low coupling via DI — controllers and services depend on names resolved through the container, not on directly require()-ing one another.
  • Extensibility — the error handler, global middleware, and module items are all pluggable through the same registration APIs used for the rest of the app.

📚 API Reference

AppBuilder (default export of @silay/core)

| Method | Description | |---|---| | new AppBuilder() | Creates a builder with its own DI container, module manager, and router manager. | | registerModuleConfigs(configs: Array) | Registers and loads an array of module items (controllers, services, middleware, schemas). Returns this. | | use(middleware: Function \| string) | Adds a global middleware — either a function or the key of a middleware registered via the module system. Returns this. | | withErrorHandler(name: string) | Uses a container-registered factory as the app's error handler instead of the default. Returns this. | | buildRoutes(routes: Array, expressAppInstance?) | Builds the Express app (or reuses one you supply), applies global middleware, registers all route modules, and attaches the error handler. Returns this. | | listen(port: number, callback?: Function) | Builds routes if needed and starts the HTTP server. | | getApp() | Returns the underlying Express app, building it first if necessary. | | getContainer() | Returns the app's DIContainer instance. |

const app = new AppBuilder();
app.registerModuleConfigs([/* ... */]).use(myMiddleware).buildRoutes([myRoutes]);
app.listen(3000);

DIContainer

| Method | Description | |---|---| | registerClass(name, Class, options?) | Registers a class under name. options: { singleton?, type? }. | | registerFunction(name, fn, options?) | Registers a function under name. | | registerValue(name, value, options?) | Registers a value/instance under name. | | has(name) | Returns true if name is registered. | | get(name) | Resolves a function/value registration by name. Throws if name refers to an unresolved class. | | instantiateClass(name, dependencies) | Instantiates a registered class with the given dependencies, caching it if it's a singleton. | | getAllByType(type) | Returns a { key: instance } map of all registrations tagged with type. | | getSingletonInstances() | Returns the internal Map of cached singleton instances. |

ModuleManager

| Method | Description | |---|---| | new ModuleManager(container) | Creates a manager bound to a DIContainer. | | registerItems(items: Array) | Queues module item definitions for loading. | | loadModules() | Registers all queued items, then instantiates singletons (resolving dependencies). | | getAllModulesByType(type) | Delegates to container.getAllByType(type). | | getAllLoadedSingletonInstances() | Returns a Map of every singleton instantiated by this manager. | | getModuleInstance(key) | Resolves a single module instance from the container by key. |

RouterManager

| Method | Description | |---|---| | new RouterManager(app, container) | Creates a router manager bound to an Express app and a DIContainer. | | register(routeModule) | Queues a { configs, routes } route module for registration. | | setRoutes() | Builds an Express Router from all queued route modules and mounts it on the app. |

Built-in middleware (require('@silay/core/lib/middlewares'))

| Export | Description | |---|---| | createTrackingHeaders | Attaches req.trackingHeaders (userIp, userAgent, requestId) to the request. | | sendResponse | Adds res.sendResponse(statusCode, data) to the response. |


🛠️ Development

Silay currently has no defined npm scripts in package.json. To work on the source locally:

git clone <repository-url>
cd silay
npm install

You can link the package into a local test project with npm link to try changes against a real app.


🗺️ Roadmap

Completed

  • Core AppBuilder with routing, DI, and module registration
  • Dependency injection container (classes, functions, values; singleton resolution)
  • Declarative router with per-route middleware
  • Built-in request tracking and response helper middleware
  • Default and custom error handling

Planned

  • CLI for scaffolding modules, controllers, and routes
  • Formal configuration system
  • Test suite and testing utilities
  • Additional built-in middleware (e.g. request validation helpers)
  • Expanded documentation and guides

🤝 Contributing

Contributions are welcome:

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/my-change.
  3. Make your changes, keeping the existing module/DI/router patterns in mind.
  4. Verify the app still runs as expected against a sample project.
  5. Open a pull request describing the change and its motivation.

📄 License

Released under the ISC License (as specified in package.json).


⭐ Final Word

If Silay fits how you like to structure Node.js apps, give it a try in a real project, open issues for anything that doesn't behave as documented, and feel free to contribute improvements back.

npm install @silay/core