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

erestrust

v0.2.0

Published

Express response helpers, rate limiting, CORS, security headers & request IDs — all built-in, zero external dependencies.

Downloads

252

Readme


The Problem

Every Express project reinvents the same response patterns:

// Without erestrust — inconsistent, repetitive
res.status(200).json({ success: true, data: user });
res.status(404).json({ error: "not found" });
res.status(422).json({ errors: [{ field: 'email', msg: 'invalid' }] });
// With erestrust — clean, typed, consistent
res.ok(user);
res.notFound('User not found');
res.invalid([{ field: 'email', message: 'Invalid format' }]);

Installation

npm install erestrust

Peer dependency: Express ≥ 4.0


Quick Start

import express from 'express';
import { erestrust } from 'erestrust';

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

// ✅ 200 OK
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) return res.notFound('User not found');
  res.ok(user);
});

// ✅ 201 Created
app.post('/users', async (req, res) => {
  const user = await db.createUser(req.body);
  res.created(user);
});

// ✅ 204 No Content
app.delete('/users/:id', async (req, res) => {
  await db.deleteUser(req.params.id);
  res.noContent();
});

// ✅ Paginated
app.get('/users', async (req, res) => {
  const [users, total] = await db.findUsers(req.query);
  res.paginated(users, { page: 1, perPage: 20, total });
});

// ✅ Validation errors
app.post('/login', async (req, res) => {
  res.invalid([
    { field: 'email', message: 'Required' },
    { field: 'password', message: 'Min 8 characters' },
  ]);
});

Response Methods

Success (2xx)

| Method | Status | Description | |--------|--------|-------------| | res.ok(data, meta?) | 200 | Success response | | res.created(data, meta?) | 201 | Resource created | | res.accepted(data, meta?) | 202 | Request accepted | | res.noContent() | 204 | No content | | res.paginated(data, pagination, meta?) | 200 | Paginated list |

Client Errors (4xx)

| Method | Status | Description | |--------|--------|-------------| | res.badRequest(message?, code?) | 400 | Bad Request | | res.unauthorized(message?, code?) | 401 | Unauthorized | | res.forbidden(message?, code?) | 403 | Forbidden | | res.notFound(message?, code?) | 404 | Not Found | | res.methodNotAllowed(message?, code?) | 405 | Method Not Allowed | | res.conflict(message?, code?) | 409 | Conflict | | res.gone(message?, code?) | 410 | Gone | | res.invalid(errors, message?) | 422 | Validation Error | | res.tooManyRequests(message?, retryAfter?) | 429 | Too Many Requests |

Server Errors (5xx)

| Method | Status | Description | |--------|--------|-------------| | res.serverError(message?, code?) | 500 | Internal Server Error | | res.badGateway(message?, code?) | 502 | Bad Gateway | | res.serviceUnavailable(message?, code?) | 503 | Service Unavailable | | res.gatewayTimeout(message?, code?) | 504 | Gateway Timeout |

Generic

| Method | Description | |--------|-------------| | res.fail(status, message, code?, details?) | Any error status |


Response Shapes

Success

{
  "success": true,
  "data": { "id": 1, "name": "Alice" }
}

Error

{
  "success": false,
  "error": {
    "status": 404,
    "message": "User not found",
    "code": "USER_NOT_FOUND"
  }
}

Paginated

{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 1,
    "perPage": 20,
    "total": 100,
    "totalPages": 5,
    "hasNext": true,
    "hasPrev": false
  }
}

Error Handler

Catch thrown errors globally:

import { errorHandler } from 'erestrust';

app.use(errorHandler());
// or with options:
app.use(errorHandler({
  logger: (err, status) => myLogger.error(err),
  maskServerErrors: true, // hides 5xx messages in production (default)
}));

Security Middleware

erestrust ships with 4 zero-dependency security middleware that cover the most common Express hardening needs.

Rate Limiting

In-memory rate limiter using a token-bucket algorithm. No Redis required.

import { rateLimit } from 'erestrust';

// 100 requests per minute (default)
app.use(rateLimit());

// Custom: 10 requests per 15 seconds on auth routes
app.use('/api/auth', rateLimit({ max: 10, windowMs: 15_000 }));

// Skip health checks
app.use(rateLimit({ skip: (req) => req.path === '/health' }));

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | max | number | 100 | Max requests per window | | windowMs | number | 60000 | Time window in ms | | message | string | 'Too Many Requests' | Error message when exceeded | | keyGenerator | (req) => string | req.ip | Key to identify clients | | skip | (req) => boolean | () => false | Skip rate limiting for a request | | headers | boolean | true | Include RateLimit-* headers |

CORS

Zero-dependency CORS middleware.

import { cors } from 'erestrust';

// Reflect request origin (default)
app.use(cors());

// Specific origins
app.use(cors({ origin: ['https://example.com', 'https://app.example.com'] }));

// Dynamic origin with credentials
app.use(cors({
  origin: (origin) => origin?.endsWith('.example.com') ? origin : false,
  credentials: true,
}));

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | origin | string \| string[] \| function | reflects request | Allowed origins | | methods | string[] | ['GET','HEAD','PUT','PATCH','POST','DELETE'] | Allowed methods | | allowedHeaders | string[] | inferred | Allowed request headers | | exposedHeaders | string[] | none | Headers exposed to browser | | credentials | boolean | false | Allow credentials | | maxAge | number | 86400 | Preflight cache max age (seconds) | | preflightContinue | boolean | true | Respond 204 to OPTIONS |

Security Headers

Sets common security headers on every response. Helmet-like, zero dependencies.

import { securityHeaders } from 'erestrust';

// Sensible defaults
app.use(securityHeaders());

// Customize
app.use(securityHeaders({
  hsts: 'max-age=63072000; includeSubDomains; preload',
  contentSecurityPolicy: "default-src 'self'; script-src 'self' cdn.example.com",
}));

Default headers set:

| Header | Default Value | |--------|---------------| | X-Content-Type-Options | nosniff | | X-Frame-Options | DENY | | X-XSS-Protection | 0 (modern best practice) | | Strict-Transport-Security | max-age=31536000; includeSubDomains | | Referrer-Policy | strict-origin-when-cross-origin | | Content-Security-Policy | default-src 'self' | | X-Permitted-Cross-Domain-Policies | none | | X-DNS-Prefetch-Control | off | | X-Powered-By | removed |

Request ID

Attaches a unique request ID to every request/response. Reads from the incoming header (if trusted) or generates a new UUID.

import { requestId } from 'erestrust';

app.use(requestId());

app.get('/ping', (req, res) => {
  console.log(req.id); // '550e8400-e29b-41d4-a716-446655440000'
  res.ok({ pong: true });
});

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | header | string | 'X-Request-Id' | Header to read/write | | generator | () => string | crypto.randomUUID() | ID generator function | | trustProxy | boolean | true | Trust incoming request ID header |

Full Setup Example

import express from 'express';
import { erestrust, errorHandler, cors, rateLimit, securityHeaders, requestId } from 'erestrust';

const app = express();

// Security middleware
app.use(requestId());
app.use(securityHeaders());
app.use(cors({ origin: 'https://myapp.com', credentials: true }));
app.use(rateLimit({ max: 100, windowMs: 60_000 }));

// Response helpers
app.use(erestrust());

// Routes
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) return res.notFound('User not found');
  res.ok(user);
});

// Error handler (last)
app.use(errorHandler());

TypeScript

All methods are fully typed. To get full autocompletion on res.* helpers, add the type augmentation import:

// In your app entry or a global .d.ts file:
import 'erestrust/express';

// Now res.ok(), res.created(), res.notFound(), etc. are typed on Express `Response`

The middleware itself:

import { erestrust, errorHandler, cors, rateLimit, securityHeaders, requestId } from 'erestrust';

Exported Types

import type {
  HttpStatusCode,
  FieldError,
  ApiSuccess,
  ApiError,
  ApiPaginated,
  ApiResponse,
  ErestrustMethods,
  ErrorHandlerOptions,
  RateLimitOptions,
  CorsOptions,
  SecurityHeadersOptions,
  RequestIdOptions,
} from 'erestrust';

| Type | Description | |------|-------------| | HttpStatusCode | Union of supported HTTP status codes | | FieldError | { field: string; message: string; code?: string } | | ApiSuccess<T> | { success: true; data: T; meta?: Record<string, unknown> } | | ApiError | { success: false; error: { status, message, code?, details?, errors? } } | | ApiPaginated<T> | Success shape with data: T[] and pagination object | | ApiResponse<T> | Union of ApiSuccess<T> \| ApiError \| ApiPaginated<T> | | ErestrustMethods | Interface describing all response helper methods | | ErrorHandlerOptions | Options for errorHandler()logger, maskServerErrors | | RateLimitOptions | Options for rateLimit()max, windowMs, keyGenerator, etc. | | CorsOptions | Options for cors()origin, methods, credentials, etc. | | SecurityHeadersOptions | Options for securityHeaders() — per-header toggles | | RequestIdOptions | Options for requestId()header, generator, trustProxy |

STATUS_TEXT

A lookup object mapping HTTP status codes to their standard text:

import { STATUS_TEXT } from 'erestrust';

STATUS_TEXT[200]; // 'OK'
STATUS_TEXT[404]; // 'Not Found'
STATUS_TEXT[500]; // 'Internal Server Error'

License

MIT