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

framework-guard

v1.2.0

Published

Express middleware toolkit: JWT auth, Zod validation, error handling, CORS/Helmet, request IDs/logging.

Readme

framework-guard

npm version CI types license

framework-guard is an Express-first middleware toolkit that ships JWT auth, error handling, request context, logging, security headers, rate limiting, guarded async handlers, and Zod validation in one cohesive package. The library builds as native ESM (with CommonJS compatibility) and includes full TypeScript types.

Features

  • Request context + correlation IDs through createRequestContext and enhanced requestId, attached to req.context, res.locals, logs, and errors.
  • Guard-aware rate limiting with rateLimit, built-in memory store, first-party Redis store support, and pluggable key derivation (user/API key/IP/request ID).
  • Async-safe handlers with guarded(handler) so thrown errors become AppErrors and successful responses auto-wrap in jsonSuccess.
  • JWT auth middleware (+ helpers signJwt, verifyJwt) with customizable token extraction, optional cookie fallback, claim verification, and request attachment.
  • Resilient error surface via AppError, errorHandler, notFound, and JSON response helpers.
  • Security middleware wrappers (withCors, withHelmet) and request observability (logRequests).
  • withCors can hydrate allowed origins from CORS_ORIGINS, exposes the request id header by default, and rejects wildcard+credentials misconfiguration.
  • withHelmet supports api/web presets so API apps can drop CSP while keeping the rest of Helmet's defaults.
  • Zod-powered validation (validate) for body, query, and params.
  • Dual ESM/CJS bundles, declaration files, and sourcemaps generated via tsup.

Requirements

  • Node.js ≥ 24 (tested on Node 24.x in CI). An .nvmrc is included for local parity.
  • npm ≥ 10 (or pnpm/yarn with the equivalent lifecycle scripts).
  • TypeScript 5.6+ if you consume the source types directly.

Installation

npm install framework-guard express jsonwebtoken cors helmet zod

For contributors:

npm install
npm run verify

Environment Variables

| Variable | Description | Example | | --- | --- | --- | | NODE_ENV | Enables production optimizations in Express and logging | production | | JWT_SECRET | Symmetric secret (or base64) for signing/verifying tokens | super-secret-value | | JWT_ISSUER | Optional issuer used when signing/verifying JWTs | framework-guard | | JWT_AUDIENCE | Optional audience used when signing/verifying JWTs | framework-guard-clients | | REDIS_URL | Connection URL for Redis-backed rate limiting | redis://localhost:6379 | | COOKIE_NAME | Cookie name used when jwtAuth reads tokens from cookies | session | | LOG_LEVEL | Propagated to your logger (pino, etc.) | info | | REQUEST_ID_HEADER | Override default X-Request-Id header name | X-Correlation-Id | | TRUST_REQUEST_ID | Set to false to always mint IDs instead of trusting headers | false | | TRUST_PROXY | Express trust proxy value when running behind a load balancer | 1 | | CORS_ORIGINS | Comma-separated origins consumed by withCors() when origin/origins are not provided | https://app.example.com,https://admin.example.com |

Document these in your README or .env.example when publishing downstream packages.

Available Scripts

  • npm run build – clean + bundle ESM/CJS artifacts via tsup (targeting Node 24).
  • npm run lint – ESLint with @typescript-eslint and import ordering rules.
  • npm run type-checktsc --noEmit for strict typing.
  • npm run test – Vitest (unit + integration). Split commands exist as test:unit / test:integration.
  • npm run verify – Convenience script (lint + type-check + tests) used in CI and prepublishOnly.
  • npm run example:express – Runs the Express sample at examples/express-basic.ts.
  • npm run release – Executes semantic-release (invoked by GitHub Actions release.yml).
  • npm run audit – Fails fast on high-severity vulnerabilities via npm audit --audit-level=high.

Usage

Express API

import express from 'express';
import pino from 'pino';
import { z } from 'zod';
import {
  createRequestContext,
  errorHandler,
  guarded,
  jwtAuth,
  logRequests,
  notFound,
  rateLimit,
  signJwt,
  validate,
  withCors,
  withHelmet,
} from 'framework-guard';

const logger = pino({ level: process.env.LOG_LEVEL ?? 'info' });
const JWT_SECRET = process.env.JWT_SECRET ?? 'change-me';
const JWT_ISSUER = process.env.JWT_ISSUER ?? 'framework-guard';
const JWT_AUDIENCE = process.env.JWT_AUDIENCE ?? 'framework-guard-clients';
const app = express();

app.use(express.json());
app.use(
  withCors({
    requestIdHeader: process.env.REQUEST_ID_HEADER ?? 'X-Request-Id',
  }),
);
app.use(withHelmet({ preset: 'api' }));
app.use(
  createRequestContext({
    header: process.env.REQUEST_ID_HEADER ?? 'X-Request-Id',
    trustHeader: process.env.TRUST_REQUEST_ID !== 'false',
  }),
);
app.use(logRequests({ logger }));

app.post('/login', (req, res) => {
  const { username } = req.body ?? {};
  if (!username) {
    return res.status(400).json({ success: false, error: { message: 'username required' } });
  }
  const token = signJwt({ sub: username }, JWT_SECRET, {
    algorithm: 'HS256',
    expiresIn: '1h',
    issuer: JWT_ISSUER,
    audience: JWT_AUDIENCE,
  });
  res.json({ success: true, data: { token } });
});

app.use(
  '/api',
  rateLimit({
    windowMs: 60_000,
    max: 100,
    key: (ctx) => (ctx.context?.user as { sub?: string } | undefined)?.sub ?? ctx.ip,
  }),
);

app.use(
  '/api',
  jwtAuth({
    secret: JWT_SECRET,
    algorithms: ['HS256'],
    verifyOptions: {
      issuer: JWT_ISSUER,
      audience: JWT_AUDIENCE,
    },
    requestProperty: 'user',
  }),
);

app.get(
  '/api/me',
  guarded(async (req) => {
    return { user: req.context?.user, requestId: req.context?.requestId };
  }),
);

const echoBody = z.object({ message: z.string().min(1) });
app.post(
  '/api/echo',
  validate({ body: echoBody }),
  guarded(async (req) => ({ body: req.body, user: req.context?.user })),
);

app.use(notFound());
app.use(errorHandler());

app.listen(3000, () => logger.info('listening on http://localhost:3000'));

If you keep JWTs in cookies instead of the Authorization header, pass cookieName:

app.use(
  '/api',
  jwtAuth({
    secret: JWT_SECRET,
    cookieName: 'session',
    algorithms: ['HS256'],
  }),
);

Redis-backed rate limiting

For multi-instance deployments, replace the in-memory store with RedisRateLimitStore:

import { createClient } from 'redis';
import { RedisRateLimitStore, rateLimit } from 'framework-guard';

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

app.use(
  rateLimit({
    windowMs: 60_000,
    max: 100,
    store: new RedisRateLimitStore({
      client: redis,
      prefix: 'framework-guard:rate-limit:',
    }),
  }),
);

RedisRateLimitStore uses incr, pttl/pTTL, pexpire/pExpire, and optional del, so it works with common Redis clients such as redis and ioredis.

Production API stack

For a full production-style example with cookie auth, Redis-backed rate limiting, request context, CORS, and Helmet, see docs/production-stack.md.

Serverless / Edge usage

The same middleware composes nicely in serverless runtimes (Vercel, AWS Lambda, Cloudflare Workers with adapters). Example with Vercel’s @vercel/node entry:

import type { VercelRequest, VercelResponse } from '@vercel/node';
import express from 'express';
import serverlessHttp from 'serverless-http';
import { requestId, logRequests, withCors, withHelmet, errorHandler, notFound } from 'framework-guard';

const app = express();
app.use(express.json());
app.use(requestId());
app.use(logRequests({ logger: console }));
app.use(withCors());
// `withCors()` will also read `CORS_ORIGINS` automatically when present.
app.use(withHelmet({ preset: 'api' }));
// register routes + middleware
app.use(notFound());
app.use(errorHandler());

const handler = serverlessHttp(app);
export default (req: VercelRequest, res: VercelResponse) => handler(req, res);

For AWS Lambda or Docker images, pair the middleware with your preferred adapter (e.g., aws-serverless-express, @apollo/server). Ensure NODE_ENV=production, and set LOG_LEVEL / JWT_SECRET through your secret manager of choice.

Middleware Ordering

Use this default order unless you have a specific reason to change it:

  1. express.json() and other body parsers
  2. withCors(...)
  3. withHelmet(...)
  4. createRequestContext(...) or requestId()
  5. logRequests(...)
  6. unauthenticated route-specific rate limits such as /auth
  7. jwtAuth(...)
  8. authenticated route-specific rate limits such as /api
  9. validate(...)
  10. route handlers wrapped in guarded(...)
  11. notFound()
  12. errorHandler()

Two important tradeoffs:

  • Put rateLimit(...) before jwtAuth(...) when you want IP-based abuse protection for unauthenticated traffic.
  • Put rateLimit(...) after jwtAuth(...) when you want limits keyed by authenticated user claims.

Testing Strategy

  • Unit tests live under tests/unit and mock Express primitives to focus on middleware behavior.
  • Integration tests (see tests/integration/middleware-stack.spec.ts) spin up an actual Express app, run Supertest through JWT/validation stacks, and assert real HTTP responses.
  • Run npm run test:unit, npm run test:integration, or npm run test (all suites). CI executes both plus lint/type-check/build/audit on every push and PR.

Documentation & OpenAPI

  • docs/production-checklist.md captures the full production-hardening checklist (Node/TypeScript setup, testing, security).
  • docs/production-stack.md shows a production-style Express stack with cookie auth, Redis-backed rate limiting, request context, CORS, and Helmet.
  • docs/openapi.md (coming from zod-openapi) documents how to generate openapi.json directly from your Zod schemas and validate requests/responses with express-openapi-validator.
  • Link to your spec (e.g., /openapi.json) in downstream READMEs and optionally serve Swagger UI or Redoc at /docs.

Security, Performance & Observability

  • Use structured logging (Pino) instead of console.log to avoid synchronous stdio in production. The logRequests middleware accepts any logger with an info method and automatically includes correlation IDs when createRequestContext runs first.
  • Keep middleware async-friendly—do not add blocking operations or synchronous crypto in hot paths. Offload CPU-heavy work to job queues/workers.
  • Run npm run audit regularly and enable Dependabot/Snyk for dependency drift.
  • Set security headers via withHelmet; use preset: 'api' for JSON APIs or preset: 'web'/default Helmet behavior for browser apps. Configure strict CORS with withCors, and keep JWT secrets in a secret manager (never committed). Avoid credentials: true with wildcard origins; withCors now throws on that misconfiguration.
  • Use RedisRateLimitStore instead of the default in-memory store when you run more than one API instance.
  • Run behind a proxy (NGINX, Cloudflare) for TLS, caching, and gzip/Brotli compression. Set app.set('trust proxy', ...) correctly so IP-based rate limits, secure cookies, and logs behave as expected.

Release & CI

  • .github/workflows/ci.yml runs lint, type-check, unit/integration tests, build, and audit on Node 24.x.
  • .github/workflows/release.yml listens for successful CI runs on main and executes semantic-release, which bumps versions, updates CHANGELOG.md, publishes to npm, and tags Git.
  • Versioning follows SemVer with Conventional Commits. Start at 1.0.0 for the first stable release, and document breaking changes plus migration notes in the changelog. Request context, guarded handlers, and rate limiting landed as SemVer-minor additions in the 1.x line.

Contributing & Support

  • See CONTRIBUTING.md for workflow details.
  • Security issues? Please open a private advisory via GitHub Security Advisories or email the maintainer listed in package.json.
  • For a deeper production hardening guide, read docs/production-checklist.md.

Request Context & Guarded Middleware Cheatsheet

import { createRequestContext, guarded, rateLimit } from 'framework-guard';

app.use(
  createRequestContext({
    header: 'X-Correlation-Id',
    generate: 'uuid',
    enrich: (req, ctx) => ({ metadata: { route: req.originalUrl } }),
  }),
);

app.use(
  rateLimit({
    windowMs: 15 * 1000,
    max: 50,
    key: (ctx) => ctx.user?.id ?? ctx.apiKey ?? ctx.ip,
  }),
);

app.get(
  '/orders',
  guarded(async (req) => fetchOrders({ userId: req.context?.user?.id })),
);

All downstream middleware (errorHandler, logRequests, etc.) share the same context so correlation IDs and user data propagate everywhere.