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

@lara-node/core

v0.1.19

Published

Lara-Node IoC container, Application, and ServiceProvider

Readme

@lara-node/core

IoC container, application bootstrap, service providers, FormRequest, middleware stack, and config system for Lara-Node.

Installation

pnpm add @lara-node/core

Quick Start

import "reflect-metadata";
import { Application, container } from "@lara-node/core";
import { AppServiceProvider }    from "./app/Providers/AppServiceProvider";
import { RouteServiceProvider }  from "./app/Providers/RouteServiceProvider";

const app = new Application(container);

app.register(AppServiceProvider);
app.register(RouteServiceProvider);

await app.boot();
app.listen(3000, () => console.log("Listening on :3000"));

Application

The main application class. Wraps an Express instance, a service provider registry, and the IoC container.

import { Application, container } from "@lara-node/core";

const app = new Application(container);

// Register service providers manually (in order)
app.register(AppServiceProvider);
app.register(DatabaseServiceProvider);
app.register(RouteServiceProvider);

// OR auto-discover all @Provider()-decorated classes
app.discoverProviders();

// Boot all registered providers (calls each provider's boot() method)
await app.boot();

// Mount a router at a path prefix
app.mountRoutes("/api", apiRouter.build());
app.mountRoutes("/",    webRouter.build());

// Get the underlying Express app
const express = app.getExpressApp();

// Get the http.Server (available after listen())
const server = app.getHttpServer();

// Listen
app.listen(3000);
app.listen(3000, () => console.log("Ready"));

Container

Lightweight IoC container with automatic constructor injection via reflect-metadata.

import { container } from "@lara-node/core";

// Bind a factory (new instance on every resolve)
container.bind("mailer", () => new MailService());

// Bind a singleton (same instance every time)
container.singleton("db",       () => new DatabaseService());
container.singleton(AuthService);               // resolves constructor params automatically
container.singleton(IUserRepository, UserRepository); // interface → implementation

// Resolve
const db   = container.make<DatabaseService>("db");
const auth = container.make(AuthService);

// Alias one binding to another key
container.alias("cache", "CacheManager");

// Check if a binding exists
container.has("mailer"); // boolean

@Injectable()

Marks a class for automatic constructor injection. Requires emitDecoratorMetadata: true in tsconfig.json and import "reflect-metadata" at the entry point.

import { Injectable } from "@lara-node/core";

@Injectable()
export class AuthService {
  constructor(
    private readonly userRepo: UserRepository,
    private readonly cache:    CacheService,
  ) {}

  async login(email: string, password: string) { /* ... */ }
}

@Inject(token)

Manually specify a resolution token when the inferred type is not enough.

import { Injectable, Inject } from "@lara-node/core";

@Injectable()
export class MailService {
  constructor(@Inject("smtp-config") private readonly config: SmtpConfig) {}
}

ServiceProvider

Base class for all service providers. Override register() to bind things into the container and boot() to run logic after all providers are registered.

import { ServiceProvider } from "@lara-node/core";

export class AppServiceProvider extends ServiceProvider {
  register(): void {
    // Bind services into the container
    this.singleton(UserRepository);
    this.singleton(AuthService);
    this.app.container.bind("stripe", () => new StripeClient(process.env.STRIPE_KEY!));

    // Register config namespaces
    this.setConfig("app", {
      name:  process.env.APP_NAME  ?? "Lara-Node",
      env:   process.env.NODE_ENV  ?? "production",
      debug: process.env.APP_DEBUG === "true",
    });
  }

  async boot(): Promise<void> {
    // Safe to resolve bindings here — all providers are registered
    const auth = this.app.container.make(AuthService);
    await auth.initialize();
  }
}

Lifecycle hooks

export class DatabaseServiceProvider extends ServiceProvider {
  register(): void {
    this.singleton("db", () => new Database());

    // Run before boot()
    this.booting(() => console.log("About to boot database..."));

    // Run after boot()
    this.booted(() => console.log("Database booted."));
  }

  async boot(): Promise<void> {
    await this.app.container.make<Database>("db").connect();
  }
}

Middleware registration (in ServiceProvider)

export class MiddlewareServiceProvider extends ServiceProvider {
  register(): void {}

  boot(): void {
    // Register individual aliases
    this.middlewareAliases({
      auth:    AuthMiddleware,
      can:     CanMiddleware,
      role:    RoleMiddleware,
      throttle: ThrottleMiddleware,
    });

    // Define middleware groups
    this.middlewareGroup("api", ["throttle:120,1", "auth"]);
    this.middlewareGroup("web", ["session", "csrf"]);

    // Prepend / append to an existing group
    this.appendMiddlewareToGroup("api", "log");
    this.prependMiddlewareToGroup("web", "cors");

    // Execution priority for named middleware
    this.middlewarePriority(["auth", "can", "role"]);

    // Treat a middleware class as a singleton (instantiated once)
    this.singletonMiddleware(AuthMiddleware);
  }
}

@Provider(name?)

Auto-discovery decorator. Classes decorated with @Provider() are picked up by app.discoverProviders().

import { ServiceProvider, Provider } from "@lara-node/core";

@Provider()
export class AppServiceProvider extends ServiceProvider {
  register(): void {
    this.singleton(UserRepository);
  }
}

Import providers in the desired registration order before calling discoverProviders():

import "./app/Providers/AppServiceProvider";
import "./app/Providers/DatabaseServiceProvider";
import "./app/Providers/RouteServiceProvider";

app.discoverProviders();

this.make<T>(abstract) — resolve from container

export class AppServiceProvider extends ServiceProvider {
  boot(): void {
    const mailer = this.make(MailService);   // resolved from IoC container
    mailer.setDefault("smtp");
  }
}

commands() — register CLI commands

Providers can expose console commands so they live next to the feature that owns them instead of being hard-coded in the console module.

export class QueueServiceProvider extends ServiceProvider {
  commands(): Array<new () => unknown> {
    return [QueueWorkCommand, ScheduleRunCommand];
  }
}

The ConsoleKernel calls provider.commands() on every booted provider automatically.

static publishes() — declare publishable files

Packages can expose files for consumers to copy via pnpm artisan vendor:publish.

export class MailServiceProvider extends ServiceProvider {
  static publishes(): Array<{ tag: string; from: string; to: string }> {
    return [
      { tag: "config",     from: "config/mail.config.ts",     to: "config/mail.config.ts" },
      { tag: "migrations", from: "migrations/001_mails.ts",   to: "database/migrations/001_mails.ts" },
    ];
  }
}

Any package (not just @lara-node/*) can declare publishable files this way.

loadMigrationsFrom(path) — register migration directory

export class BlogServiceProvider extends ServiceProvider {
  register(): void {
    this.loadMigrationsFrom(path.join(__dirname, "../migrations"));
  }
}

mergeConfigFrom(path, namespace) — set package defaults

Called in package providers to supply defaults that the application can override with setConfig().

export class MailServiceProvider extends ServiceProvider {
  register(): void {
    this.mergeConfigFrom(path.join(__dirname, "../config/mail.defaults.js"), "mail");
  }
}

callAfterResolving(abstract, callback) — post-resolution hook

export class LogServiceProvider extends ServiceProvider {
  register(): void {
    this.callAfterResolving(MailService, (instance) => {
      (instance as MailService).setLogger(this.make(Logger));
    });
  }
}

Deferred providers

A provider is deferred if provides() returns a non-empty array. It will only be booted when one of those services is first resolved.

export class MailServiceProvider extends ServiceProvider {
  provides(): string[] {
    return ["mailer"];
  }

  when(): string[] {
    return ["mailer"];   // only boot when MailService is first resolved
  }

  register(): void {
    this.singleton("mailer", () => new MailService());
  }
}

FormRequest

Typed, validated HTTP request. Extend it, declare rules(), and type your controller's first parameter — the router resolves, validates, and injects the instance automatically.

Raw Express req: The same input-helper methods (req.all(), req.input(), req.isSecure(), req.bearerToken(), etc.) are also available directly on the Express Request object when RequestExtenderMiddleware from @lara-node/middlewares is in the global middleware stack. FormRequest and the raw req share the same FormRequest interface — the difference is that FormRequest also runs authorize() and validated() automatically.

import { FormRequest } from "@lara-node/core";

class CreateUserRequest extends FormRequest<{ name: string; email: string; password: string }> {
  rules() {
    return {
      name:     "required|string|min:2|max:100",
      email:    "required|email|unique:users,email",
      password: "required|password|confirmed",
    };
  }

  messages(): Record<string, string> {
    return {
      "email.unique":    "This email address is already taken.",
      "password.confirmed": "Passwords do not match.",
    };
  }

  authorize(): boolean {
    return !!this.user();   // reject unauthenticated requests
  }
}

// In your controller:
async store(req: CreateUserRequest, res: Response) {
  const { name, email, password } = req.validated();
  const user = await User.create({ name, email, password: await hash(password) });
  return res.status(201).json({ data: user });
}

If authorize() returns false a 422/401 response is sent automatically. If validation fails a 422 response with the error map is sent automatically.

Direct property access

FormRequest uses a Proxy so you can access input fields directly as properties:

async store(req: CreateUserRequest, res: Response) {
  console.log(req.name);   // same as req.input("name")
  console.log(req.email);  // same as req.input("email")
}

Input retrieval

| Method | Description | |---|---| | all() | All merged input — body + query + params + any merged data | | input(key?, default?) | Single value or all input when called without a key | | post(key?, default?) | Body-only input | | json(key?, default?) | Alias for post() | | only(...keys) | Subset of input — only the specified keys | | except(...keys) | Input minus the specified keys | | keys() | Array of all input key names | | intersect(...keys) | Keys present in both the input and the given list |

const all   = req.all();
const email = req.input<string>("email");
const body  = req.post();
const name  = req.input("name", "Anonymous");

const safe  = req.only("name", "email");
const rest  = req.except("password", "password_confirmation");

Type-cast helpers

| Method | Returns | Notes | |---|---|---| | string(key, default?) | string | String(value) | | integer(key, default?) | number | parseInt — returns default on NaN | | float(key, default?) | number | parseFloat — returns default on NaN | | boolean(key, default?) | boolean | Truthy: "true"/"1"/"yes"/"on" | | date(key) | Date \| null | Returns null on invalid dates | | collect(key?) | T[] | Wraps scalar in array; returns arrays as-is |

const age     = req.integer("age", 0);
const price   = req.float("price", 0.0);
const active  = req.boolean("active");
const created = req.date("created_at");
const tags    = req.collect<string>("tags");

Presence checks

| Method | Description | |---|---| | has(...keys) | All specified keys exist in the input | | hasAny(...keys) | At least one of the specified keys exists | | filled(...keys) | All specified keys exist and are non-empty (trims strings) | | isNotFilled(...keys) | Inverse of filled() | | missing(...keys) | All specified keys are absent from the input |

if (req.has("role"))          assignRole(req.input("role"));
if (req.hasAny("bio", "about")) updateProfile();
if (req.filled("phone"))      validatePhone(req.string("phone"));
if (req.missing("referral"))  skipReferralStep();

Conditional helpers

// whenHas(key, found, notFound?)
req.whenHas("role",   (role) => assignRole(role));
req.whenHas("plan",   (plan) => applyPlan(plan), () => applyFreePlan());

// whenFilled(key, filled, empty?)
req.whenFilled("bio",    (bio) => updateBio(bio), () => clearBio());

// whenMissing(key, missing, present?)
req.whenMissing("otp",   ()    => sendOtp(),       (otp) => verifyOtp(otp));

Mutation

// Add or overwrite keys
req.merge({ tenant_id: 42, processed_by: "system" });

// Add only when the key is absent
req.mergeIfMissing({ locale: "en", currency: "USD" });

// Replace all input entirely
req.replace({ id: 1, name: "Alice" });

Files

const avatar  = req.file("avatar");            // UploadedFile | null
const hasFile = req.hasFile("document");        // boolean
const files   = req.allFiles();                 // Record<string, UploadedFile | UploadedFile[]>

// UploadedFile shape:
// { fieldname, originalname, encoding, mimetype, size, buffer?, path?, filename? }

Headers & cookies

const apiKey = req.header("x-api-key");           // string | undefined
const hasAuth = req.hasHeader("authorization");    // boolean
const token   = req.bearerToken();                 // string | null — extracts Bearer token

const session = req.cookie("session_id");          // string | undefined
const hasCookie = req.hasCookie("session_id");     // boolean
const allCookies = req.cookies();                  // Record<string, string>

Request type / content negotiation

req.isMethod("POST")    // boolean — case-insensitive
req.isJson()            // Content-Type: application/json
req.wantsJson()         // Accept: application/json
req.expectsJson()       // isJson() || wantsJson()
req.ajax()              // X-Requested-With: XMLHttpRequest
req.isPjax()            // PJAX request (ajax + X-PJAX header)
req.isPrefetch()        // sec-purpose: prefetch
req.isSecure()          // HTTPS or x-forwarded-proto: https

URL helpers

req.fullUrl()                                // "https://api.example.com/users?page=2"
req.fullUrlWithQuery({ sort: "name" })       // appends / overwrites query params
req.fullUrlWithoutQuery("page", "sort")      // removes specified query params

req.root()                                   // "https://api.example.com"
req.host()                                   // "api.example.com"
req.httpHost()                               // "api.example.com:8080" (includes port)
req.scheme()                                 // "https" | "http"
req.schemeAndHttpHost()                      // "https://api.example.com"

req.decodedPath()                            // URL-decoded path
req.segments()                               // ["api", "users", "42"]
req.segment(1)                               // "api"  (1-indexed)
req.segment(3, "default")                    // "42"

req.pathIs("/api/*")                         // wildcard path matching
req.routeIs("/api/users/*")                  // alias of pathIs()

Client info

req.ip                          // string | undefined (getter)
req.ips()                       // string[] — x-forwarded-for chain, newest first
req.userAgent()                 // string | undefined
req.fingerprint()               // SHA-256 of method + url + ip + user-agent

req.server("request_method")   // "GET" | "POST" | ...
req.server("remote_addr")       // client IP
req.server("query_string")      // raw query string
req.server("request_uri")       // full URI

Authenticated user

req.user()              // unknown | undefined — value set by auth middleware
req.user<UserModel>()   // typed overload

Passthrough getters

req.body            // req body object
req.query           // query string object
req.params          // route params
req.headers         // headers map
req.method          // "GET" | "POST" | ...
req.path            // "/api/users"
req.url             // "/api/users?page=2"
req.originalUrl     // "/api/users?page=2" (unchanged by middleware)
req.getRequest()    // underlying Express Request

Config

Dot-notation key-value store. Namespaces are typically registered in service providers.

import { config, setConfig, mergeConfig, hasConfig, allConfig } from "@lara-node/core";

// Register a namespace
setConfig("app", {
  name:     process.env.APP_NAME  ?? "My API",
  env:      process.env.NODE_ENV  ?? "production",
  debug:    process.env.APP_DEBUG === "true",
  url:      process.env.APP_URL   ?? "http://localhost:3000",
  key:      process.env.APP_KEY,
  timezone: "UTC",
});

// Read values
const name   = config("app.name");
const debug  = config<boolean>("app.debug", false);
const driver = config("db.default", "mysql");

// Check existence
if (hasConfig("mail.host")) { /* ... */ }

// Deep-merge package defaults (app setConfig() still wins)
mergeConfig("mail", { default: "smtp", from: { address: "[email protected]" } });

// Dump everything
const all = allConfig();

Config file pattern

config/app.ts:

import { setConfig } from "@lara-node/core";

setConfig("app", {
  name:  process.env.APP_NAME  ?? "My API",
  env:   process.env.NODE_ENV  ?? "production",
  debug: process.env.APP_DEBUG === "true",
});

Load before app.boot():

import "./config/app";
import "./config/database";
import "./config/mail";

Middleware registration

import { registerMiddleware, resolveMiddleware,
         getRegisteredMiddleware, hasMiddleware } from "@lara-node/core";

// Register
registerMiddleware("auth",     JwtMiddleware);
registerMiddleware("throttle", ThrottleMiddleware);

// Resolve (returns the handler or an array of handlers)
const handler = resolveMiddleware("auth");

// Inspect
hasMiddleware("auth");           // boolean
getRegisteredMiddleware();       // Map<string, ...>

Helpers

dd(...values)

Dump-and-die. Pretty-prints every argument then terminates the process. Useful for quick debugging.

import { dd } from "@lara-node/core";

dd(user);
dd("label", req.body, result);

clone(value)

Deep clone any value. Handles plain objects, class instances, functions, arrays, Date, RegExp, Map, Set, ArrayBuffer, TypedArray, symbol-keyed properties, and circular references.

import { clone } from "@lara-node/core";

const copy = clone(original);

// Class instances — prototype chain preserved
class Point { constructor(public x: number, public y: number) {} }
const p  = new Point(1, 2);
const p2 = clone(p);
p2.x = 99;
console.log(p.x);            // 1 — original untouched
console.log(p2 instanceof Point); // true

| Input | Behaviour | |---|---| | Primitives | Returned as-is | | Plain objects | Deep copy of all own properties | | Class instances | Object.create(proto)instanceof preserved | | Functions | New wrapper; own properties deep-cloned | | Date / RegExp | New instance with same value | | Map / Set | New instance with deep-cloned entries | | ArrayBuffer / TypedArray / DataView | Buffer copy | | Circular references | Handled — no infinite loop | | Getters / setters | Preserved as descriptors, not invoked |


Notes

  • reflect-metadata must be imported once, as early as possible in your entry file, before any decorator-annotated class is loaded.
  • container.singleton(SomeClass) uses TypeScript's emitted constructor metadata to resolve parameters automatically — no manual factory needed.
  • discoverProviders() is optional — use app.register() for explicit control over registration order.
  • FormRequest.authorize() defaults to true. Override it to reject unauthorized requests before validation runs.
  • FormRequest merges body, query, and route params in that priority order. Use merge() / mergeIfMissing() to inject server-side values (e.g. authenticated user id) before validation.