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

node-responder

v1.2.1

Published

Modern, standardized API response middleware for Express.js — with TypeScript support, pagination, and shorthand methods.

Readme

node-responder

Modern, standardized API response middleware for Express.js

npm version npm downloads license TypeScript zero dependencies

Stop writing repetitive res.status(200).json({ success: true, data: ... }) in every route. node-responder adds clean, consistent response helpers directly to Express's res object.


✨ Features

  • Zero dependencies — only Express as a peer dependency
  • TypeScript support — full type definitions included
  • ESM + CommonJS — works with both require and import
  • Request Logger — logs method, URL, status, and response time to terminal
  • Pagination built-inres.paginate() with full meta
  • asyncHandler — write async routes without try-catch boilerplate
  • Consistent format — every response follows the same structure
  • Node.js 14+ supported

📦 Installation

npm install node-responder

🚀 Quick Start

const express = require("express");
const responder = require("node-responder");

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

// Apply middleware globally
app.use(responder());

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

app.listen(3000);

📋 Response Format

Every response follows the same consistent structure:

Success Response

{
  "success": true,
  "message": "Success",
  "data": { "id": 1, "name": "John" },
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000Z",
    "statusCode": 200
  }
}

Error Response

{
  "success": false,
  "message": "User not found",
  "data": null,
  "errors": null,
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000Z",
    "statusCode": 404
  }
}

Paginated Response

{
  "success": true,
  "message": "Users fetched",
  "data": [{ "id": 1 }, { "id": 2 }],
  "meta": {
    "timestamp": "2025-01-15T10:30:00.000Z",
    "statusCode": 200,
    "pagination": {
      "page": 1,
      "limit": 10,
      "total": 100,
      "totalPages": 10,
      "hasNextPage": true,
      "hasPrevPage": false
    }
  }
}

⚙️ Middleware Setup

const responder = require("node-responder");

// Apply to all routes
app.use(responder());

// With logger enabled
app.use(responder({ logger: true }));

// Apply to a specific router only
router.use(responder());

Options

| Option | Type | Default | Description | | -------- | --------- | ------- | ------------------------------------------------------------------------- | | logger | boolean | false | Logs each request to terminal with method, URL, status, and response time |


📖 API Reference

Quick Reference

| Method | Status | When to use | | -------------------------------------------- | ------ | ------------------------------------------ | | res.ok(data?, message?) | 200 | Successful GET request | | res.created(data?, message?) | 201 | New resource created | | res.noContent() | 204 | Delete or update with no data to return | | res.success(data?, message?, statusCode?) | custom | Custom success status code | | res.badRequest(message?, errors?) | 400 | Validation failed | | res.unauthorized(message?) | 401 | Not logged in or no token | | res.forbidden(message?) | 403 | Logged in but no permission | | res.notFound(message?) | 404 | Resource does not exist | | res.conflict(message?) | 409 | Duplicate data (e.g. email already exists) | | res.unprocessable(message?, errors?) | 422 | Business logic validation failed | | res.tooManyRequests(message?, retryAfter?) | 429 | Rate limit exceeded | | res.serverError(message?) | 500 | Unexpected server-side error | | res.error(message?, statusCode?, errors?) | custom | Custom error status code | | res.paginate(data, message?, pagination?) | 200 | Paginated list response |


✅ Success Methods


res.ok(data?, message?)

Status: 200 — Use for standard successful GET requests.

// With data only
res.ok({ id: 1, name: "John" });

// With data and custom message
res.ok({ id: 1, name: "John" }, "User fetched successfully");

// Response:
// { success: true, message: "User fetched successfully", data: { id: 1, name: "John" }, meta: { statusCode: 200, ... } }

res.created(data?, message?)

Status: 201 — Use when a new resource has been successfully created.

const user = await User.create({ name: "John", email: "[email protected]" });

res.created(user);

// With custom message
res.created(user, "Account created successfully");

// Response:
// { success: true, message: "Created successfully", data: { ...user }, meta: { statusCode: 201, ... } }

res.noContent()

Status: 204 — Use after a successful delete or update when no data needs to be returned.

await User.findByIdAndDelete(req.params.id);
res.noContent();

// Response: empty body (HTTP 204 No Content)

res.success(data?, message?, statusCode?)

Status: custom — Use when you need a custom success status code.

res.success({ accepted: true }, "Request accepted", 202);

// Response:
// { success: true, message: "Request accepted", data: { accepted: true }, meta: { statusCode: 202, ... } }

❌ Error Methods


res.badRequest(message?, errors?)

Status: 400 — Use when the request is malformed or validation fails.

// Simple message
res.badRequest("Name is required");

// With validation errors object
res.badRequest("Validation failed", {
  name: "Name is required",
  email: "Invalid email format",
});

// Response:
// { success: false, message: "Validation failed", data: null, errors: { name: "...", email: "..." }, meta: { statusCode: 400, ... } }

res.unauthorized(message?)

Status: 401 — Use when the user is not authenticated (no token or invalid token).

// Default message
res.unauthorized();

// Custom message
res.unauthorized("Please login to continue");

// Response:
// { success: false, message: "Unauthorized", data: null, errors: null, meta: { statusCode: 401, ... } }

res.forbidden(message?)

Status: 403 — Use when the user is authenticated but does not have permission.

// Default message
res.forbidden();

// Custom message
res.forbidden("You do not have permission to access this resource");

// Response:
// { success: false, message: "Forbidden", data: null, errors: null, meta: { statusCode: 403, ... } }

res.notFound(message?)

Status: 404 — Use when a requested resource does not exist.

const user = await User.findById(req.params.id);

if (!user) {
  return res.notFound("User not found");
}

// Response:
// { success: false, message: "User not found", data: null, errors: null, meta: { statusCode: 404, ... } }

res.conflict(message?)

Status: 409 — Use when the request conflicts with existing data (e.g. duplicate email).

const exists = await User.findOne({ email: req.body.email });

if (exists) {
  return res.conflict("Email already registered");
}

// Response:
// { success: false, message: "Email already registered", data: null, errors: null, meta: { statusCode: 409, ... } }

res.unprocessable(message?, errors?)

Status: 422 — Use when the data format is correct but business logic validation fails.

res.unprocessable("Cannot process this request", {
  age: "Must be at least 18 years old",
});

// Response:
// { success: false, message: "Cannot process this request", data: null, errors: { age: "..." }, meta: { statusCode: 422, ... } }

res.tooManyRequests(message?, retryAfter?)

Status: 429 — Use when a client exceeds the rate limit.

// Basic usage
res.tooManyRequests("Too many requests, please slow down");

// With retryAfter in seconds — sets the Retry-After header automatically
res.tooManyRequests("Rate limit exceeded", 60);

// Response:
// Header: Retry-After: 60
// { success: false, message: "Rate limit exceeded", data: null, errors: null, meta: { statusCode: 429, ... } }

res.serverError(message?)

Status: 500 — Use when an unexpected server-side error occurs.

try {
  await someRiskyOperation();
} catch (err) {
  console.error(err);
  return res.serverError("Something went wrong, please try again later");
}

// Response:
// { success: false, message: "Something went wrong, please try again later", data: null, errors: null, meta: { statusCode: 500, ... } }

res.error(message?, statusCode?, errors?)

Status: custom — Use when you need a custom error status code.

res.error("Gone", 410);
res.error("Validation failed", 400, { field: "required" });

📄 Pagination — res.paginate(data, message?, pagination?)

Use for returning paginated lists with full pagination metadata.

router.get("/users", async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const skip = (page - 1) * limit;

  const [users, total] = await Promise.all([
    User.find().skip(skip).limit(limit),
    User.countDocuments(),
  ]);

  return res.paginate(users, "Users fetched", { page, limit, total });
});

// Response:
// {
//   success: true,
//   message: "Users fetched",
//   data: [...users],
//   meta: {
//     timestamp: "...",
//     statusCode: 200,
//     pagination: {
//       page: 1, limit: 10, total: 100,
//       totalPages: 10,
//       hasNextPage: true,
//       hasPrevPage: false
//     }
//   }
// }

| Pagination Field | Type | Description | | ---------------- | -------- | ------------------------- | | page | number | Current page number | | limit | number | Number of items per page | | total | number | Total number of documents |


⚡ asyncHandler

Eliminates try-catch boilerplate from every async route. Errors are automatically forwarded to Express's next(err).

const { asyncHandler } = require("node-responder");

// ❌ Before — repetitive try-catch in every route:
router.get("/users", async (req, res) => {
  try {
    const users = await User.find();
    res.ok(users);
  } catch (err) {
    res.serverError(err.message);
  }
});

// ✅ After — clean and concise with asyncHandler:
router.get(
  "/users",
  asyncHandler(async (req, res) => {
    const users = await User.find(); // errors automatically go to next(err)
    res.ok(users);
  }),
);

// Catch all errors in one global error handler:
app.use((err, req, res, next) => {
  console.error(err);
  res.serverError(err.message);
});

📝 Logger — { logger: true }

Logs every incoming request to the terminal with method, URL, status code, and response time.

app.use(responder({ logger: true }));

Terminal output:

GET     /api/users                          200  23ms  ✔
POST    /api/users                          201  45ms  ✔
GET     /api/users/abc123                   404  12ms  ✖
POST    /api/auth/login                     401  8ms   ✖
DELETE  /api/products/999                   500  5ms   ✖

Tip: Enable logger in development, disable in production:

app.use(
  responder({
    logger: process.env.NODE_ENV !== "production",
  }),
);

🔧 Real-World MERN Example

const express = require("express");
const responder = require("node-responder");
const { asyncHandler } = require("node-responder");

const router = express.Router();
router.use(responder({ logger: true }));

// GET /api/users?page=1&limit=10
router.get(
  "/",
  asyncHandler(async (req, res) => {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 10;
    const skip = (page - 1) * limit;

    const [users, total] = await Promise.all([
      User.find().skip(skip).limit(limit).select("-password"),
      User.countDocuments(),
    ]);

    return res.paginate(users, "Users fetched", { page, limit, total });
  }),
);

// GET /api/users/:id
router.get(
  "/:id",
  asyncHandler(async (req, res) => {
    const user = await User.findById(req.params.id).select("-password");
    if (!user) return res.notFound("User not found");
    return res.ok(user, "User fetched");
  }),
);

// POST /api/users
router.post(
  "/",
  asyncHandler(async (req, res) => {
    const { name, email, password } = req.body;

    const errors = {};
    if (!name) errors.name = "Name is required";
    if (!email) errors.email = "Email is required";
    if (!password) errors.password = "Password is required";

    if (Object.keys(errors).length > 0) {
      return res.badRequest("Validation failed", errors);
    }

    const exists = await User.findOne({ email });
    if (exists) return res.conflict("Email already registered");

    const user = await User.create({ name, email, password });

    return res.created(
      { id: user._id, name: user.name, email: user.email },
      "Account created successfully",
    );
  }),
);

// PUT /api/users/:id
router.put(
  "/:id",
  asyncHandler(async (req, res) => {
    const user = await User.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
    });
    if (!user) return res.notFound("User not found");
    return res.ok(user, "User updated successfully");
  }),
);

// DELETE /api/users/:id
router.delete(
  "/:id",
  asyncHandler(async (req, res) => {
    const user = await User.findByIdAndDelete(req.params.id);
    if (!user) return res.notFound("User not found");
    return res.noContent();
  }),
);

module.exports = router;

TypeScript Usage

import express from "express";
import responder, { asyncHandler } from "node-responder";

const app = express();
app.use(express.json());
app.use(responder({ logger: true }));

app.get(
  "/users",
  asyncHandler(async (req, res) => {
    const users = await User.find();
    res.ok(users, "Users fetched");
  }),
);

// Global error handler
app.use(
  (
    err: Error,
    req: express.Request,
    res: express.Response,
    next: express.NextFunction,
  ) => {
    console.error(err);
    res.serverError(err.message);
  },
);

app.listen(3000);

🔗 Links


📄 License

MIT © Hammad Sadi