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

express-safe-kit

v0.1.0

Published

Lightweight, secure, beginner-friendly Express.js middleware kit — secure your Express app in one line.

Readme

express-safe-kit

Secure your Express app in one line.

npm version License: MIT Node


Why express-safe-kit exists

Building secure Express APIs usually means wiring together Helmet, CORS, rate limiting, body parsers, and consistent JSON responses — often with inconsistent types and heavy dependencies.

express-safe-kit gives you:

  • Sensible security defaults out of the box
  • Zero runtime dependencies
  • Express 4 and 5 support
  • Fully typed response helpers (res.success, res.error, …)
  • Standalone middleware exports when you need fine-grained control

Features

| Feature | Description | |---------|-------------| | Security headers | Manual safe defaults (no Helmet) | | CORS | Environment-aware defaults | | Rate limiting | In-memory, per-IP, auto cleanup | | Body parsers | express.json + express.urlencoded | | Response helpers | Consistent JSON success/error shapes | | Error handler | Production-safe stack hiding | | Request logger | Optional, off by default | | TypeScript | Strict types + global Response augmentation |


Installation

npm install express-safe-kit express

Requirements: Node.js 18+, Express ^4.18 or ^5.0


Quick Start

import express from "express";
import { expressSafeKit } from "express-safe-kit";

const app = express();
app.use(expressSafeKit());

app.get("/", (_req, res) => {
  res.success("API is running");
});

app.listen(3000);

Production Setup

import express from "express";
import {
  errorHandler,
  expressSafeKit,
  notFoundHandler
} from "express-safe-kit";

const app = express();

app.use(
  expressSafeKit({
    cors: {
      origin: ["https://your-frontend.com"],
      credentials: true
    },
    rateLimit: {
      max: 100,
      windowMs: 15 * 60 * 1000
    },
    requestLogger: { enabled: true }
  })
);

// ... routes

app.use(notFoundHandler());
app.use(errorHandler());

app.listen(process.env.PORT ?? 3000);

Always set NODE_ENV=production in production.


Security Recommendations

  1. Configure CORS explicitly in production (see CORS Best Practices).
  2. Use HTTPS behind a reverse proxy; HSTS is applied when the request is secure or X-Forwarded-Proto includes https.
  3. Add errorHandler() last among error middleware.
  4. Do not rely on in-memory rate limits across multiple instances — see Memory Rate Limiter Limitations.
  5. Keep Express updated — this package does not patch Express itself.

Headers set by default include:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Referrer-Policy: strict-origin-when-cross-origin
  • Content-Security-Policy (sensible default)
  • Strict-Transport-Security (HTTPS only)
  • Removes X-Powered-By

CORS Best Practices

Development

When NODE_ENV is not production and you omit cors.origin, the default is:

origin: "*"

Production

When NODE_ENV is production and cors.origin is not set:

[express-safe-kit]
CORS origin not configured.
Configure a specific origin for improved security.

Cross-origin requests are denied by default until you configure an explicit origin.

If you explicitly set origin: "*" in production:

[express-safe-kit]
Wildcard CORS origin detected in production.
Configure a specific origin for improved security.

Recommended production configuration:

expressSafeKit({
  cors: {
    origin: ["https://app.example.com"],
    credentials: true,
    methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"]
  }
})

Supported origin types: string, string[], RegExp, or (origin) => boolean.


Memory Rate Limiter Limitations

The built-in rate limiter stores counters in process memory.

Why this is limited

| Scenario | Issue | |----------|-------| | Multiple server instances | Each instance has its own counter | | Load balancers | Clients may hit different limits per instance | | Serverless / cold starts | State is not shared between invocations | | Process restarts | Counters reset |

When to use Redis (roadmap)

Use a shared store when you need consistent limits across instances:

// Planned for v0.2 — see docs/ROADMAP_V0.2.md
app.use(
  redisRateLimiter({
    redisClient,
    max: 100,
    windowMs: 900_000
  })
);

See docs/ROADMAP_V0.2.md.

MVP / single-instance

The default in-memory limiter is fine for:

  • Local development
  • Single-node deployments
  • Low-traffic internal APIs

Default: 100 requests / 15 minutes / IP with periodic expiry cleanup.


Rate Limiting Best Practices

expressSafeKit({
  rateLimit: {
    max: 50,
    windowMs: 60_000,
    message: "Slow down",
    statusCode: 429
  }
})
  • Set app.set("trust proxy", 1) when behind a reverse proxy so req.ip is accurate.
  • Disable with rateLimit: false if you use an external gateway rate limiter.
  • For distributed systems, use an external store or wait for the v0.2 Redis rate limiter (see roadmap).

Response Helpers

| Method | Status | Body | |--------|--------|------| | res.success(data, message?, status?) | 200 (default) | { success: true, message, data } | | res.created(data, message?) | 201 | { success: true, message, data } | | res.error(message, status?, errors?) | 500 (default) | { success: false, message, errors } | | res.noContent() | 204 | empty |

app.get("/users/:id", (_req, res) => {
  res.success({ id: 1 }, "User found");
});

app.post("/users", (_req, res) => {
  res.created({ id: 2 }, "User created");
});

app.get("/fail", (_req, res) => {
  res.error("Not allowed", 403, ["forbidden"]);
});

app.delete("/users/:id", (_req, res) => {
  res.noContent();
});

Error Handling

import { errorHandler, notFoundHandler } from "express-safe-kit";

app.get("/boom", () => {
  const err = Object.assign(new Error("Bad input"), {
    statusCode: 400,
    errors: ["field required"]
  });
  throw err;
});

app.use(notFoundHandler());
app.use(errorHandler());
  • Development: stack traces included in JSON.
  • Production: stack traces hidden (NODE_ENV=production).

Works with Express 5 async error propagation when errors are thrown from async route handlers.


Logger

Request logging is off by default.

expressSafeKit({
  requestLogger: {
    enabled: true,
    logFn: (line) => console.log(line)
  }
})

Output format: GET /path 200 12.34ms


TypeScript

Response helpers are typed globally — import the package once:

import { expressSafeKit } from "express-safe-kit";
// res.success, res.error, res.created, res.noContent are typed automatically

Module augmentation ships in dist/index.d.ts.


CommonJS

const express = require("express");
const { expressSafeKit } = require("express-safe-kit");

const app = express();
app.use(expressSafeKit());

ESM

import express from "express";
import { expressSafeKit } from "express-safe-kit";

package.json exports:

{
  "import": "./dist/index.js",
  "require": "./dist/index.cjs",
  "types": "./dist/index.d.ts"
}

Public API (v0.1.0)

| Export | Description | |--------|-------------| | expressSafeKit | All-in-one middleware router | | securityHeaders | Manual security headers | | corsMiddleware | CORS handling | | rateLimiter | In-memory rate limit | | responseHelpers | res.success / res.error / … | | requestLogger | Optional HTTP logger | | errorHandler | Global error JSON handler | | notFoundHandler | Standard 404 handler |

All exports work standalone:

import {
  corsMiddleware,
  errorHandler,
  notFoundHandler,
  rateLimiter,
  requestLogger,
  responseHelpers,
  securityHeaders
} from "express-safe-kit";

Full options

expressSafeKit({
  security: false | {
    contentSecurityPolicy?: string,
    hsts?: { enabled?, maxAge?, includeSubDomains?, preload? }
  },
  cors: false | {
    origin?: string | string[] | RegExp | ((origin?: string) => boolean),
    methods?: string[],
    allowedHeaders?: string[],
    exposedHeaders?: string[],
    credentials?: boolean,
    maxAge?: number
  },
  rateLimit: false | {
    windowMs?: number,
    max?: number,
    message?: string,
    statusCode?: number
  },
  jsonBody: false | { limit?: string },
  urlencodedBody: false | { extended?: boolean },
  requestLogger: false | { enabled?: boolean, logFn?: (line: string) => void }
})

FAQ

Does this replace Helmet?

It provides overlapping header protection without adding Helmet as a dependency. For advanced Helmet-specific policies, use Helmet alongside or instead of security: false.

Why zero runtime dependencies?

Smaller install, fewer supply-chain risks, and faster CI — Express is the only required peer.

Does CORS origin: "*" still work in production?

Yes, if you explicitly set cors: { origin: "*" }. The change is only for the default when origin is omitted.

Express 4 vs 5?

Both are supported via the express peer dependency. Async errors in Express 5 are handled when thrown from route handlers with errorHandler() registered.


Roadmap

See docs/ROADMAP_V0.2.md.

| Item | Target version | |------|----------------| | Redis rate limiter | v0.2 | | Request ID middleware | v0.2 | | API key middleware | v0.3 | | Zod validation | v0.3 | | Mongo sanitize / HPP | v0.4 | | Pino / Morgan adapters | v0.4 | | OpenTelemetry hooks | Future |

v0.1.0 exports stable middleware only. Roadmap features are not in the public API until fully implemented.


Contributing

See CONTRIBUTING.md.

GitHub Discussions (recommended):

  • Enable Discussions for Q&A and ideas
  • Use Issues for bugs and features
  • Use Security Advisories for vulnerabilities

Security Policy

See SECURITY.md.


License

MIT © Ahmad — see LICENSE.