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

@structrix/structrix

v1.0.0

Published

A robust, scalable base microservice framework for Node.js

Readme

Structrix

A robust, scalable microservice framework for Node.js built on top of Express. Structrix provides a structured, opinionated foundation for building production-ready microservices with built-in support for authentication, caching, rate limiting, metrics, logging, retries, and database integrations.


Table of Contents


Features

  • 🏗️ Modular architecture — register independent modules (service + controller + routes) at runtime
  • 🔐 Authentication — JWT, API key, HTTP Basic, and OAuth2 strategies out of the box
  • 🗄️ Caching — in-memory cache with TTL, tag-based invalidation, and stale-while-revalidate
  • Rate limiting — token-bucket algorithm, per-key limits, and optional Redis backend
  • 📊 Metrics — Prometheus-compatible metrics via prom-client with a built-in /metrics endpoint
  • 📝 Structured logging — Winston-powered logging with daily log rotation and JSON/text formats
  • 🔁 Retry handling — exponential backoff with configurable retryable error types
  • 🗃️ Database abstraction — pluggable providers for PostgreSQL, MySQL, and MongoDB
  • 🛡️ Security — Helmet headers, CORS, compression, and request-ID tracing built in
  • 🐳 Docker ready — includes Dockerfile and docker-compose.yml
  • 💪 TypeScript first — fully typed with strict interfaces throughout

Installation

npm install structrix

Or clone and build from source:

git clone https://github.com/structrix/structrix.git
cd structrix
npm install
npm run build

Quick Start

1. Create a Service

Extend BaseService and implement the two abstract lifecycle hooks:

import { BaseService, ServiceOptions } from "structrix";

export class UserService extends BaseService {
  constructor(options: ServiceOptions) {
    super(options);
  }

  protected async onInitialize(): Promise<void> {
    this.logger.info("UserService initialized");
  }

  protected async onShutdown(): Promise<void> {
    this.logger.info("UserService shutting down");
  }

  async getUsers() {
    return this.withCache("users:all", async () => {
      // fetch from DB ...
      return [{ id: "1", name: "Alice" }];
    });
  }
}

2. Create a Controller

Extend BaseController, set the route prefix, and implement initializeRoutes:

import { BaseController } from "structrix";
import { Request, Response } from "express";
import { UserService } from "./user.service";

export class UserController extends BaseController {
  constructor(service: UserService) {
    super("/users", service);
  }

  public initializeRoutes(): void {
    this.router.get("/", this.getUsers.bind(this));
  }

  private async getUsers(req: Request, res: Response): Promise<void> {
    await this.withErrorHandler(req, res, async () => {
      const users = await (this.service as UserService).getUsers();
      this.sendSuccess(res, users);
    });
  }
}

3. Create Routes

Extend BaseRoutes and use createRoute to declaratively define your routes with optional auth, caching, and rate limiting:

import { BaseRoutes } from "structrix";
import { UserController } from "./user.controller";

export class UserRoutes extends BaseRoutes {
  constructor(controller: UserController) {
    super(controller);
  }

  protected configureRoutes(): void {
    this.createRoute({
      path: "/",
      method: "get",
      handler: "getUsers",
      auth: true,
      cache: { ttl: 60 },
      rateLimit: true
    });
  }
}

4. Boot the Application

import Structrix from "structrix";
import { UserService } from "./user.service";
import { UserController } from "./user.controller";
import { UserRoutes } from "./user.routes";

const app = new Structrix({
  name: "my-service",
  version: "1.0.0",
  port: 3000
});

app.registerModule({
  name: "users",
  service: UserService,
  controller: UserController,
  routes: UserRoutes,
  config: {
    cache: { enabled: true, ttl: 300 }
  }
});

app.start();

Architecture

src/
├── index.ts                   # Structrix app entry point & re-exports
├── types/
│   └── global.types.ts        # Shared interfaces (ServiceConfig, HealthStatus, etc.)
├── core/
│   ├── base.service.ts        # Abstract BaseService
│   ├── base.controller.ts     # Abstract BaseController
│   ├── base.routes.ts         # Abstract BaseRoutes
│   ├── base.types.ts          # Core interfaces (IService, IController, etc.)
│   ├── base.repositories.ts   # Abstract BaseRepository
│   ├── database.providers.ts  # BaseDatabaseProvider & QueryOptions
│   └── service.factory.ts     # ServiceFactory singleton
├── config/
│   ├── app.config.ts          # AppConfiguration & extended config interfaces
│   └── database.config.ts     # Postgres, MongoDB, MySQL providers & repositories
├── middleware/
│   ├── auth.middleware.ts     # JWT / API key / Basic / OAuth2 auth
│   ├── cache.middleware.ts    # Response caching & tag invalidation
│   ├── error.middleware.ts    # Global error & 404 handlers
│   ├── logging.middleware.ts  # Request/response logging
│   └── validation.middleware.ts # Joi schema validation for body/query/params
└── utils/
    ├── cacheManager.ts        # In-memory CacheManager (node-cache)
    ├── logger.ts              # Winston logger wrapper
    ├── metrics.ts             # Prometheus MetricsCollector
    ├── rateLimiter.ts         # Token-bucket RateLimiter
    ├── retryHandler.ts        # Exponential-backoff RetryHandler
    └── validator.ts           # Joi-based Validator utility

Core Concepts

Structrix (App)

The top-level class that bootstraps Express, registers global middleware, and manages modules.

const app = new Structrix(config?: Partial<ServiceConfig>);

await app.registerModule(moduleDef);  // register a module
await app.start();                    // start listening
app.getApp();                         // access the Express instance
app.getConfig();                      // access resolved config

Built-in endpoints registered automatically:

| Endpoint | Description | | -------------- | ---------------------------------------- | | GET /health | Health status of all registered services | | GET /metrics | Prometheus metrics (when enabled) |


BaseService

Abstract base class for all services. Provides lifecycle management, logging, metrics, caching, rate limiting, and retry logic.

abstract class BaseService extends EventEmitter implements IService {
  // Lifecycle
  async initialize(): Promise<void>;
  async shutdown(): Promise<void>;
  async healthCheck(): Promise<boolean>;
  async getHealthStatus(): Promise<HealthStatus>;

  // Helpers
  async withCache<T>(
    key: string,
    fn: () => Promise<T>,
    ttl?: number
  ): Promise<T>;
  async withRetry<T>(fn: () => Promise<T>, context?: string): Promise<T>;
  async withRateLimit<T>(fn: () => Promise<T>, key?: string): Promise<T>;

  // Abstract — implement in your subclass
  protected abstract onInitialize(): Promise<void>;
  protected abstract onShutdown(): Promise<void>;
}

Properties automatically initialized from config:

| Property | Description | | -------------- | ------------------------------------- | | logger | Winston-based Logger instance | | metrics | Prometheus MetricsCollector | | cache | In-memory CacheManager (if enabled) | | rateLimiter | Token-bucket RateLimiter (if enabled) | | retryHandler | Exponential-backoff RetryHandler |


BaseController

Abstract base class for controllers. Wires up middleware, response helpers, and pagination utilities.

abstract class BaseController implements IController {
  path: string;
  router: Router;
  service: BaseService;

  // Response helpers
  protected sendSuccess<T>(
    res,
    data?,
    message?,
    pagination?,
    statusCode?
  ): Response;
  protected sendError(res, error, statusCode?, details?): Response;
  protected handleError(res, error): Response;

  // Pagination helpers
  protected getPagination(req): { page: number; limit: number };
  protected getPaginationInfo(total, page, limit): PaginationInfo;

  // Error-catching wrapper
  protected async withErrorHandler(
    req,
    res,
    handler: () => Promise<void>
  ): Promise<void>;
}

BaseRoutes

Abstract base class for route configuration. Supports declarative route definitions with built-in auth, cache, and rate-limit middleware injection.

abstract class BaseRoutes {
  protected abstract configureRoutes(): void;

  protected createRoute(config: RouteConfig): void;
  protected groupRoutes(prefix: string, routes: RouteConfig[]): void;
  public getRouter(): Router;
}

interface RouteConfig {
  path: string;
  method: "get" | "post" | "put" | "patch" | "delete";
  handler: string; // method name on your controller
  middleware?: any[];
  auth?: boolean;
  rateLimit?: boolean;
  cache?: boolean | { ttl: number };
}

ServiceFactory

Singleton that manages the lifecycle and registry of all modules.

const factory = ServiceFactory.getInstance();

await factory.createModule(moduleDef, globalConfig);
factory.getService<T>("users"); // retrieve a service by name
factory.getAllRoutes(); // all registered BaseRoutes
await factory.shutdownAll(); // graceful shutdown
await factory.getHealthStatuses(); // health of all services

Configuration

ServiceConfig is the core configuration interface. All fields except name, version, environment, and port are optional.

interface ServiceConfig {
  name: string;
  version: string;
  environment: "development" | "production" | "test";
  port: number;
  host?: string;
  timeout?: number;
  maxRetries?: number;
  rateLimit?: {
    enabled: boolean;
    requestsPerMinute: number;
    maxConcurrent?: number;
  };
  cache?: {
    enabled: boolean;
    ttl: number; // seconds
    checkPeriod?: number; // seconds
  };
  logging?: {
    level: "error" | "warn" | "info" | "debug";
    format: "json" | "text";
    output: "console" | "file" | "both";
  };
  metrics?: {
    enabled: boolean;
    collectDefaultMetrics?: boolean;
    prefix?: string;
  };
  auth?: {
    enabled: boolean;
    type: "jwt" | "apikey" | "basic" | "none";
    jwtSecret?: string;
    tokenExpiry?: string;
  };
}

Middleware

Authentication

AuthMiddleware supports four strategies. Configure via AuthConfig:

| Strategy | Description | | -------- | --------------------------------------------------------- | | jwt | Bearer token in Authorization header, signed with HS256 | | apikey | Key in a configurable header (default: x-api-key) | | basic | HTTP Basic auth with a custom validator function | | oauth2 | Bearer token validated against an external token endpoint |

// Protect a route — roles and permissions are optional
router.get(
  "/admin",
  authMiddleware.authenticate({ roles: ["admin"] }),
  handler
);

// Generate a JWT token
const token = authMiddleware.generateToken(user);

// Hash & verify passwords (bcrypt)
const hash = await authMiddleware.hashPassword("secret");
const valid = await authMiddleware.verifyPassword("secret", hash);

Caching

CacheMiddleware wraps responses in an in-memory cache. Cache keys are derived from method, path, query parameters, and optionally user ID and headers.

// Cache responses for 60 seconds
router.get("/items", cacheMiddleware.cache({ ttl: 60 }), handler);

// Invalidate cache entries matching a pattern after a write
router.post("/items", cacheMiddleware.invalidateCache("/items*"), handler);

// Serve stale content while revalidating in the background
router.get(
  "/feed",
  cacheMiddleware.staleWhileRevalidate({ ttl: 30, staleTtl: 120 }),
  handler
);

// Tag-based invalidation
router.get("/posts", cacheMiddleware.cacheWithTags(["posts"]), handler);
router.delete(
  "/posts/:id",
  cacheMiddleware.invalidateByTags(["posts"]),
  handler
);

Cache headers added to responses:

| Header | Value | | --------------- | ------------------------- | | X-Cache | HIT, MISS, or STALE | | X-Cache-Key | MD5 hash of the cache key | | Cache-Control | public, max-age=<ttl> |


Rate Limiting

Token-bucket rate limiting, configurable per key (IP by default). Supports a Redis backend for distributed environments.

// Express middleware — limits by IP
router.use(rateLimiter.middleware());

// Custom key (e.g. authenticated user ID)
router.use(rateLimiter.middleware((req) => req.user?.id ?? req.ip));

// Programmatic acquire (throws if limit exceeded)
await rateLimiter.acquire("user:123");

// Wait for a token (up to maxWaitMs)
await rateLimiter.acquireWithWait("user:123", 5000);

// Redis-backed distributed store
const store = RateLimiter.createRedisStore(redisClient);
const limiter = new RateLimiter({ requestsPerMinute: 100, store });

Response headers:

| Header | Description | | ----------------------- | ----------------------------- | | X-RateLimit-Limit | Total tokens per window | | X-RateLimit-Remaining | Remaining tokens | | X-RateLimit-Reset | Unix timestamp of next refill |


Logging

LoggingMiddleware logs every request and response with timing, request ID, and optional body capture.

Configured via ServiceConfig.logging:

| Option | Values | Default | | -------- | -------------------------------- | --------- | | level | error, warn, info, debug | info | | format | json, text | text | | output | console, file, both | console |

Log files rotate daily, kept for 14 days at up to 20 MB per file (in logs/).


Validation

ValidationMiddleware validates request data against Joi schemas and returns structured 400 errors.

import Joi from "joi";

const schema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string().email().required()
});

router.post("/users", validationMiddleware.validateBody(schema), handler);

// Also available:
validationMiddleware.validateQuery(schema);
validationMiddleware.validateParams(schema);

Error Handling

ErrorMiddleware provides a global error handler and a 404 catcher. Register it last on the Express app.

All unhandled errors are returned as:

{
  "success": false,
  "error": "Error message",
  "metadata": {
    "timestamp": "2026-01-01T00:00:00.000Z",
    "requestId": "uuid"
  }
}

Utilities

Logger

Winston-based structured logger with console and/or rotating file transports.

const logger = new Logger({ level: "info", format: "json", output: "both" });

logger.info("User created", { userId: "123" });
logger.warn("Slow query detected", { duration: 450 });
logger.error("Database connection failed", { error });
logger.debug("Cache miss", { key: "users:all" });

MetricsCollector

Prometheus-compatible metrics using prom-client. Registers a set of built-in HTTP, cache, database, queue, and error metrics automatically.

// Increment a counter
metrics.increment("http_requests_total", {
  method: "GET",
  path: "/users",
  status: "200"
});

// Set a gauge
metrics.setGauge("queue_size", 42, { queue_name: "emails" });

// Observe a histogram
metrics.observe("http_request_duration_seconds", 0.123, {
  method: "GET",
  path: "/users"
});

// Time an async function
const result = await metrics.time(
  "database_query_duration_seconds",
  async () => {
    return db.query("SELECT * FROM users");
  },
  { query_type: "select", collection: "users" }
);

// Get Prometheus text output
const output = await metrics.getMetrics();

Built-in metrics (prefixed by ServiceConfig.metrics.prefix):

| Metric | Type | Description | | ---------------------------------- | --------- | --------------------- | | _http_requests_total | Counter | Total HTTP requests | | _http_request_duration_seconds | Histogram | Request duration | | _http_requests_active | Gauge | Active requests | | _errors_total | Counter | Total errors | | _cache_operations_total | Counter | Cache hits/misses | | _database_query_duration_seconds | Histogram | DB query duration | | _queue_size | Gauge | Current queue depth | | _queue_processed_total | Counter | Processed queue items |


RateLimiter

Token-bucket rate limiter with in-memory or Redis-backed store.

const limiter = new RateLimiter({
  requestsPerMinute: 60,
  maxConcurrent: 10
});

const info = await limiter.acquire("user:123");
// info.remaining — tokens left
// info.resetTime — epoch ms when bucket refills

await limiter.reset("user:123"); // reset a specific key
await limiter.resetAll(); // reset all (memory store only)

RetryHandler

Exponential backoff with configurable max delay and retryable error types.

const retryHandler = new RetryHandler({
  maxRetries: 3,
  backoffFactor: 2,
  initialDelay: 500, // ms
  maxDelay: 10000 // ms
});

const result = await retryHandler.execute(
  () => externalApi.fetchData(),
  "fetchData" // context label for logs
);

Retry delays: 500ms → 1000ms → 2000ms (with backoffFactor: 2, initialDelay: 500).

Network errors (ECONNRESET, ETIMEDOUT) are always retried. Additional retryable error classes can be passed via retryableErrors.


CacheManager

Thin wrapper over node-cache with an async interface and set membership helpers for tag-based invalidation.

const cache = new CacheManager({ ttl: 300, checkPeriod: 60 });

await cache.set("users:all", data, 60);
const data = await cache.get<User[]>("users:all");
await cache.del("users:all");

// Set membership (used by CacheMiddleware for tag invalidation)
await cache.sadd("tag:users", "users:all");
const keys = await cache.smembers("tag:users");

await cache.close();

Validator

Joi-based validator utility.

const validator = new Validator();

const schema = Joi.object({ name: Joi.string().required() });
const isValid = await validator.validate({ name: "Alice" }, schema);
const errors = validator.getErrors();

Database

Providers

Three database providers are available. Each extends BaseDatabaseProvider.

import { DatabaseFactory } from './config/database.config';

const provider = DatabaseFactory.createProvider({
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'user',
  password: 'pass',
  database: 'mydb'
});

await provider.connect();
const conn = provider.getConnection();
const client = conn.getClient();               // raw pg.Pool / mongoose / mysql2 pool

// Transactions
await conn.transaction(async (session) => {
  await session.query('INSERT INTO ...', [...]);
});

await provider.healthCheck();  // true / false
await provider.disconnect();

| Type | Package | Notes | | ---------- | ---------- | --------------------------------- | | postgres | pg | Connection pool via pg.Pool | | mongodb | mongoose | Replica set & auth source support | | mysql | mysql2 | Connection pool |

The DatabaseManager singleton manages multiple named providers:

import { DatabaseManager } from "./config/database.config";

const db = DatabaseManager.getInstance();
await db.initializeDefault();
await db.registerProvider("analytics", analyticsDbConfig);

const primary = db.getProvider();
const analytics = db.getProvider("analytics");
await db.healthCheckAll();
await db.disconnectAll();

Repositories

Extend BaseRepository<T> to build type-safe data access layers. MongoRepository and PostgresRepository implementations are provided.

// MongoDB
class UserMongoRepository extends MongoRepository<User> {
  constructor(provider: MongoDBProvider) {
    super(provider, "User", userSchema);
  }
}

const repo = new UserMongoRepository(mongoProvider);
const user = await repo.findById("abc123");
const users = await repo.findAll({ sort: { createdAt: -1 }, limit: 20 });
const created = await repo.create({
  name: "Alice",
  email: "[email protected]"
});
await repo.update("abc123", { name: "Alicia" });
await repo.delete("abc123");

QueryOptions:

| Option | Description | | ------------- | --------------------------------------- | | limit | Max records to return | | offset | Records to skip | | sort | { field: 1 \| -1 } sort order | | select | Fields to include | | populate | Relations to populate (MongoDB) | | lean | Return plain objects (no model methods) | | transaction | Session/client to use for transaction |


Environment Variables

| Variable | Default | Description | | ----------------------- | ------------- | -------------------------------------- | | PORT | 3000 | HTTP port | | HOST | 0.0.0.0 | Bind address | | NODE_ENV | development | Environment | | TIMEOUT | 30000 | Request timeout (ms) | | MAX_RETRIES | 3 | Default max retries | | LOG_LEVEL | info | error | warn | info | debug | | LOG_FORMAT | text | json | text | | LOG_OUTPUT | console | console | file | both | | RATE_LIMIT_ENABLED | false | Enable rate limiting | | RATE_LIMIT_REQUESTS | 60 | Requests per minute | | RATE_LIMIT_CONCURRENT | 10 | Max concurrent requests | | CACHE_ENABLED | false | Enable response caching | | CACHE_TTL | 300 | Cache TTL in seconds | | CACHE_CHECK_PERIOD | 60 | Cache cleanup interval (seconds) | | METRICS_ENABLED | false | Enable Prometheus metrics | | METRICS_PREFIX | structrix | Metric name prefix | | AUTH_ENABLED | false | Enable authentication | | AUTH_TYPE | jwt | jwt | apikey | basic | none | | JWT_SECRET | — | Secret key for JWT signing | | TOKEN_EXPIRY | 24h | JWT expiry (ms format string) | | ALLOWED_ORIGINS | * | Comma-separated CORS origins |

Copy .env.example to .env to get started:

cp .env.example .env

Docker

Build and run with Docker Compose (includes the app service):

# Build image
npm run docker:build

# Start services
npm run docker:up

# Stop services
npm run docker:down

Or with plain Docker:

docker build -t structrix .
docker run -p 3000:3000 --env-file .env structrix

API Endpoints

All module routes are mounted under /api. The exact sub-paths depend on the path you set in your BaseController constructor.

| Endpoint | Method | Description | | ---------- | ------ | ----------------------------------------- | | /health | GET | Service health status (all modules) | | /metrics | GET | Prometheus metrics (when metrics enabled) | | /api/** | * | Module routes (registered dynamically) |


License

MIT