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/middlewares

v0.1.21

Published

Predefined class-based middleware for Lara-Node applications

Downloads

224

Readme

@lara-node/middlewares

Built-in Express middleware classes — async context, request logging, request helpers, validation, JWT auth, response helpers, and RBAC guards.

Installation

pnpm add @lara-node/middlewares

Quick Start

import express from "express";
import {
  asyncContext,
  requestLogger,
  requestExtender,
  validatorAttach,
  responseExtender,
  errorHandler,
} from "@lara-node/middlewares";

const app = express();

app.use(express.json());
app.use(asyncContext);     // async context per request
app.use(requestLogger);    // colorized request log
app.use(requestExtender);  // attaches all FormRequest helpers to req
app.use(validatorAttach);  // attaches req.validate()
app.use(responseExtender); // auto-serializes Model instances

// ... routes ...

app.use(errorHandler); // must be last — handles ValidationError and other errors

Middleware Reference

All middleware classes implement handle(req, res, next) and expose a toHandler() method that returns a bound Express handler.

AsyncContextMiddleware

Wraps each request in an AsyncLocalStorage context so the request and authenticated user are accessible anywhere in the call stack without prop-drilling.

import { asyncLocalStorage } from "@lara-node/middlewares";

// Anywhere in the request lifecycle (services, repositories, etc.)
const store = asyncLocalStorage.getStore(); // { req, user? }

function getCurrentUser() {
  return asyncLocalStorage.getStore()?.user ?? null;
}

RequestLoggerMiddleware

Colorized output on response finish: METHOD PATH STATUS TIME IP [user:id]. Green for 2xx, yellow for 4xx, red for 5xx.


RequestExtenderMiddleware

Attaches all Laravel-style FormRequest helper methods directly to the raw Express req object. Must run before your controllers.

import { requestExtender } from "@lara-node/middlewares";

app.use(requestExtender);

Once registered, every req in your controllers has the full helper API described below.

Input retrieval

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

const all   = req.all();
const email = req.input<string>("email");
const body  = req.post();
const safe  = req.only("name", "email");
const rest  = req.except("password");

Type-cast helpers

| Method | Returns | Notes | |---|---|---| | req.string(key, default?) | string | String(value) | | req.integer(key, default?) | number | parseInt — returns default on NaN | | req.float(key, default?) | number | parseFloat — returns default on NaN | | req.boolean(key, default?) | boolean | Truthy: true / 1 / "1" / "true" | | req.date(key) | Date \| null | Returns null on invalid dates | | req.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 flag  = req.boolean("active");
const tags  = req.collect<string>("tags");

Presence checks

| Method | Description | |---|---| | req.has(...keys) | All specified keys exist in the input | | req.hasAny(...keys) | At least one key exists | | req.filled(...keys) | All keys exist and are non-empty | | req.isNotFilled(...keys) | All keys are absent or empty | | req.missing(...keys) | All keys are absent |

if (req.has("role"))         assignRole(req.input("role"));
if (req.filled("phone"))     validatePhone(req.string("phone"));
if (req.missing("referral")) skipReferralStep();

Conditional helpers

req.whenHas("role",    (role) => assignRole(role));
req.whenFilled("bio",  (bio)  => updateBio(bio), () => clearBio());
req.whenMissing("otp", ()     => sendOtp(),       (otp) => verifyOtp(otp));

Mutation

req.merge({ tenant_id: 42 });               // add / overwrite keys
req.mergeIfMissing({ locale: "en" });       // add only when absent
req.replace({ id: 1, name: "Alice" });      // replace all input

Files (multer)

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

Headers & cookies

const ok      = req.hasHeader("x-api-key");   // boolean
const token   = req.bearerToken();             // string | null
const session = req.cookie("session_id");      // string | undefined
const hasCk   = req.hasCookie("session_id");   // boolean
const cookies = req.allCookies();              // 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()           // X-PJAX header present
req.isPrefetch()       // purpose: prefetch header
req.isSecure()         // req.secure || protocol === "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.httpHost()                           // "api.example.com"
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.pathIs("/api/*")                     // wildcard path matching
req.routeIs("users.*")                   // matches route name pattern

Client info

req.userAgent()               // string | undefined
req.fingerprint()             // base64 of ip | user-agent | host
req.server("x-forwarded-for") // any request header value

ValidatorMiddleware

Attaches req.validate(rules) to every request. Throws ValidationError on failure (caught by ErrorHandlerMiddleware → HTTP 422).

// In a controller
const data = await req.validate({
  name: "required|string|min:2|max:100",
  email: "required|email",
  age: "required|integer|min:18",
});

ResponseExtenderMiddleware

Adds res.jsonAsync() and overrides res.json() to automatically call toJSONAsync() on @lara-node/db Model instances, handling hidden fields, casts, and loaded relations.

Also adds convenience response helpers:

res.success(data, "Created", 201);       // { success: true, data, message }
res.created(data);                        // 201
res.noContent();                          // 204
res.badRequest("Invalid input");          // 400
res.unauthorized();                       // 401
res.forbidden();                          // 403
res.notFound("User not found");           // 404
res.unprocessableEntity(errors);          // 422
res.serverError();                        // 500
res.json(user);                     // Model auto-serialized
res.json([user1, user2]);            // array of Models
res.json(await User.paginate(15));   // paginated result
await res.jsonAsync(user);           // explicit async path

AuthMiddleware

Verifies Authorization: Bearer <token> using JWT and optionally loads the full user from the database.

import { AuthMiddleware } from "@lara-node/middlewares";

const auth = new AuthMiddleware({
  userLoader: async (uid) => {
    const user = await User.with(["roles", "roles.permissions"]).find(uid);
    if (!user) return null;
    return {
      id: user.id,
      roles: user.roles.map((r) => r.slug),
      permissions: user.roles.flatMap((r) => r.permissions.map((p) => p.slug)),
    };
  },
  decryptToken: (token) => myDecrypt(token), // optional
}).toHandler();

app.use("/api", auth);

When userLoader is not provided, req.user is populated directly from the JWT payload.

AuthorizeByStatusMiddleware

Rejects requests where the authenticated user's status is not active.

import { authorizeByStatus } from "@lara-node/middlewares";

app.use("/api/protected", auth, authorizeByStatus);

ErrorHandlerMiddleware

Express 4-argument error handler. Handles:

  • ValidationError — HTTP 422 with { success: false, errors, messages }
  • err.status present — responds with that status code
  • All others — HTTP 500

Stack trace is included in non-production responses.

import { errorHandler } from "@lara-node/middlewares";

app.use(errorHandler); // must be registered after all routes

RBAC helpers

import { authorizeRoles, authorizePermissions } from "@lara-node/middlewares";

router.get("/admin",        auth, authorizeRoles("admin", "moderator"), handler);
router.delete("/users/:id", auth, authorizePermissions("delete_users"),  handler);

Registering middleware aliases

import { registerMiddleware } from "@lara-node/core";
import { AuthMiddleware } from "@lara-node/middlewares";

registerMiddleware("auth", AuthMiddleware);

Or use the @Middleware('alias') decorator on the class:

import { Middleware } from "@lara-node/router";

@Middleware("auth")
export class JwtMiddleware { ... }

Writing custom middleware

import { Request, Response, NextFunction } from "express";

export class MaintenanceModeMiddleware {
  handle(req: Request, res: Response, next: NextFunction): void {
    if (process.env.MAINTENANCE === "true") {
      res.status(503).json({ message: "Service temporarily unavailable." });
      return;
    }
    next();
  }

  toHandler() {
    return (req: Request, res: Response, next: NextFunction) => this.handle(req, res, next);
  }
}

Express type augmentation

This package augments the Express Request and Response interfaces globally via the FormRequest interface:

// Request helpers (available after RequestExtenderMiddleware runs)
req.all()
req.input("key")
req.isSecure()
req.bearerToken()
req.isJson()
// ...all FormRequest methods

// Auth (set by AuthMiddleware)
req.user?.id
req.user?.roles
req.user?.permissions

// Validation (set by ValidatorMiddleware)
const data = await req.validate<{ email: string }>(rules);

// Response helpers (set by ResponseExtenderMiddleware)
await res.jsonAsync(model);
res.success(data);
res.notFound("User not found");

The create-lara-node scaffold generates a src/types/express.d.ts that re-exports these augmentations automatically.


Recommended middleware order

AsyncContextMiddleware     — AsyncLocalStorage per request
RequestLoggerMiddleware    — log method/path/status/time
RequestExtenderMiddleware  — attach all FormRequest helpers to req
ValidatorMiddleware        — attach req.validate()
ResponseExtenderMiddleware — auto-serialize Models, add res.success() etc.

Error handler must be last, after all routes:

app.use(errorHandler);

Environment Variables

| Variable | Default | Description | |---|---|---| | JWT_SECRET | — | Secret key used by AuthMiddleware to verify JWTs |