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

@qnx/response

v0.8.8

Published

Collection of async functions for express response

Downloads

603

Readme

@qnx/response

@qnx/response is a library designed to simplify handling HTTP responses within Express.js applications. It offers standardized formatting and transmission of responses, including built-in support for error management and validation.

🤖 MCP Server: https://qnx-mcp-server.vercel.app/mcp/response

❗ The Problem

Every async Express route ends up looking like this:

router.post('/users', async (req, res) => {
  try {
    const user = await User.create(req.body)
    res.status(201).json({ data: user, message: 'Created.' })
  } catch (err) {
    res.status(500).json({ error: 'Internal Server Error' })
  }
})

Multiply that across 50 routes and you have 50 copies of the same try/catch, the same res.json() boilerplate, and inconsistent response shapes.

✅ The Solution

Wrap your handler with asyncValidatorHandler. Return your data — the library formats the response and catches any errors automatically.

import { asyncValidatorHandler, initializeApiResponse } from '@qnx/response'

router.post('/users', asyncValidatorHandler(async (req) => {
  const user = await User.create(req.body)
  return initializeApiResponse().setData(user).setMessage('Created.')
}))
  • 🚫 No try/catch — errors are caught and formatted automatically
  • 📤 No res.json() — just return your data
  • 📦 Consistent shape — every route returns { data } or { error, errors }
  • 🧠 TypeScript-first — fully typed, excellent IntelliSense

📑 Table of Contents

🚀 Features

| Feature | Description | | ----------------------- | ---------------------------------------------------------------- | | 📦 Standard Format | Unified response structure across APIs | | ✅ Validation Support | Works with Zod and express-validator to handle validation errors | | 🚫 No Try-Catch Needed | Automatically handles async errors | | ⚠️ Custom Error Support | Create and throw validation errors easily | | 🧠 TypeScript-First | Fully typed API with excellent IntelliSense support | | ⚙️ Configurable | Flexible enough to adjust error structures and codes |

📦 Installation

# Using npm
npm install @qnx/response

# Using yarn
yarn add @qnx/response

# Using pnpm
pnpm install @qnx/response

# Using bun
bun install @qnx/response

Peer Dependencies

@qnx/response requires @qnx/errors as a peer dependency:

npm install @qnx/errors

💡 Usage

All examples use asyncValidatorHandler as the outer wrapper. Everything you return is sent as the response body.

Return data

Return any value and it is automatically wrapped as { data: ... }:

import { asyncValidatorHandler } from '@qnx/response'

router.get('/users', asyncValidatorHandler(async (req) => {
  return await User.findAll()
}))
{ "data": [...] }

Return with a message

Use initializeApiResponse() to attach a message or set a custom status code:

import { asyncValidatorHandler, initializeApiResponse } from '@qnx/response'

router.post('/users', asyncValidatorHandler(async (req) => {
  const user = await User.create(req.body)
  return initializeApiResponse().setData(user).setMessage('User created successfully.')
}))
{ "data": { "id": 1, "name": "Alice" }, "message": "User created successfully." }

Throw a single field error

Use InvalidValueError to signal that a specific field is invalid. The handler catches it and sends a 400 response:

import { asyncValidatorHandler } from '@qnx/response'
import { InvalidValueError } from '@qnx/errors'

router.get('/users/:id', asyncValidatorHandler(async (req) => {
  const user = await User.findById(req.params.id)
  if (!user) throw new InvalidValueError('User not found.', { key: 'id' })
  return user
}))
{ "error": "User not found.", "errors": { "id": ["User not found."] } }

Throw multiple field errors

Use ApiResponseErrorsValue to collect multiple field errors, then throw ValidationError:

import { asyncValidatorHandler, ApiResponseErrorsValue, initializeApiResponse } from '@qnx/response'
import { ValidationError } from '@qnx/errors'

router.post('/register', asyncValidatorHandler(async (req) => {
  const { email, password } = req.body

  const errors = ApiResponseErrorsValue.getInstance()
  if (!email) errors.addError('email', 'Email is required.')
  if (!password) errors.addError('password', 'Password is required.')
  if (errors.hasErrors()) throw new ValidationError('Validation failed', { errRes: { errors: errors.getErrors() } })

  const user = await User.create({ email, password })
  return initializeApiResponse().setData(user).setMessage('Registered.')
}))
{
  "error": "Validation failed",
  "errors": {
    "email": ["Email is required."],
    "password": ["Password is required."]
  }
}

Zod validation

Zod errors thrown inside asyncValidatorHandler are caught and automatically converted into the standard { error, errors } shape:

import { asyncValidatorHandler, initializeApiResponse } from '@qnx/response'
import { z } from 'zod'

const CreateUserSchema = z.object({
  name: z.string({ required_error: 'Name is required.' }),
  email: z.string({ required_error: 'Email is required.' }).email('Invalid email.')
})

router.post('/users', asyncValidatorHandler(async (req) => {
  const body = CreateUserSchema.parse(req.body)
  const user = await User.create(body)
  return initializeApiResponse().setData(user).setMessage('User created successfully.')
}))

Valid input { "name": "Alice", "email": "[email protected]" }:

{ "data": { "id": 1, "name": "Alice", "email": "[email protected]" }, "message": "User created successfully." }

Invalid input { "email": "[email protected]" } — missing name:

{ "error": "Name is required.", "errors": { "name": ["Name is required."] } }

🔄 Migrating Existing Routes

Callback-style DB query

// Before
router.get('/users', (req, res) => {
  connection.query('SELECT * FROM users', (err, rows) => {
    if (err) return res.status(500).json({ error: err.message })
    res.json({ data: rows })
  })
})

// After — use an async DB client or promisify the callback
router.get('/users', asyncValidatorHandler(async (req) => {
  return await db.query('SELECT * FROM users')
}))

async/await with try/catch

// Before
router.post('/users', async (req, res) => {
  try {
    const user = await User.create(req.body)
    res.status(201).json({ data: user, message: 'Created.' })
  } catch (err) {
    res.status(500).json({ error: err.message })
  }
})

// After — remove try/catch entirely; errors are caught automatically
router.post('/users', asyncValidatorHandler(async (req) => {
  const user = await User.create(req.body)
  return initializeApiResponse().setData(user).setMessage('Created.')
}))

Promise chains (.then/.catch)

// Before
router.get('/users/:id', (req, res) => {
  User.findById(req.params.id)
    .then(user => {
      if (!user) return res.status(404).json({ error: 'Not found' })
      res.json({ data: user })
    })
    .catch(err => res.status(500).json({ error: err.message }))
})

// After
router.get('/users/:id', asyncValidatorHandler(async (req) => {
  const user = await User.findById(req.params.id)
  if (!user) throw new InvalidValueError('User not found.', { key: 'id' })
  return user
}))

next(err) error forwarding

// Before
router.put('/users/:id', async (req, res, next) => {
  try {
    const updated = await User.update(req.params.id, req.body)
    res.json({ data: updated })
  } catch (err) {
    next(err)
  }
})

// After — asyncValidatorHandler handles errors inline; next() is no longer needed
router.put('/users/:id', asyncValidatorHandler(async (req) => {
  const updated = await User.update(req.params.id, req.body)
  return initializeApiResponse().setData(updated).setMessage('Updated successfully.')
}))

📘 API Reference

asyncValidatorHandler(fn)

Wraps an async route handler. Catches all thrown errors, formats responses, and removes the need for res.json() or try/catch.

asyncValidatorHandler(async (req, res) => {
  // return data  → sent as { data: ... }
  // throw error  → caught, formatted as { error, errors }
})

initializeApiResponse()

Creates a chainable response builder. Return the result from inside asyncValidatorHandler.

✅ Success & Data

| Method | Description | Example | | ----------------------- | ------------------------------------ | ---------------------------------------------- | | .setData(data) | Sets the response data | .setData({ user: { name: 'Alice' } }) | | .setMessage(message) | Sets a success message | .setMessage('User fetched successfully.') |

⚠️ Error Management

| Method | Description | Example | | ----------------------- | ------------------------------------ | ---------------------------------------------- | | .setError(message) | Sets a top-level error message | .setError('Internal Server Error') | | .setErrors(errors) | Sets field-level errors | .setErrors({ email: ['Invalid format'] }) | | .setErrorCode(code) | Sets a custom error code string | .setErrorCode('E123') |

📦 Additional Meta

| Method | Description | Example | | ----------------------- | ------------------------------------ | ---------------------------------------------- | | .setStatusCode(code) | Sets the HTTP status code | .setStatusCode(201) | | .setAdditional(data) | Attaches extra metadata to the body | .setAdditional({ traceId: 'xyz-001' }) |


ApiResponseErrorsValue

Builds field-level error objects in { field: [messages] } format.

✏️ Add / Set Errors

| Method | Description | Example | | ------------------------- | -------------------------------------------- | ----------------------------------------- | | .getInstance() | Returns a new instance | | | .addError(key, message) | Appends a message to a field's error list | .addError('email', 'Invalid format') | | .setError(key, message) | Replaces all messages for a field | .setError('email', 'Required') | | .hasErrors() | Returns true if any errors have been added | |

📤 Get Structured Errors

| Method | Returns | Example | | --------------------- | ------------------------------------ | --------------------- | | .getErrors() | { field: [messages] } | .getErrors() | | .getErrorResponse() | { errors: { field: [messages] } } | .getErrorResponse() |


Error Classes

Throw these inside asyncValidatorHandler — the handler catches them and sends the formatted response automatically.

| Class | Use case | | ----- | -------- | | InvalidValueError(message, { key }) | Single field validation error | | ValidationError(message, { errRes: { errors } }) | Multiple field errors — pair with ApiResponseErrorsValue | | ApiError(message, statusCode, { errRes }?) | Any other HTTP status — e.g. throw new ApiError('Conflict detected.', 409) responds 409 { message } |

Error Response Helpers

Call these directly only where throwing is not an option — e.g. plain Express middleware outside asyncValidatorHandler. Inside the handler, throw the error classes above instead.

invalidValueApiResponse(res, key, message) — deprecated

Deprecated: throw InvalidValueError instead — it produces the identical response, needs no res, and halts execution:

import { InvalidValueError } from '@qnx/errors'

router.get('/users/:id', asyncValidatorHandler(async (req) => {
  const user = await User.findById(req.params.id)
  if (!user) throw new InvalidValueError('User not found.', { key: 'id' })
  return user
}))
{ "error": "User not found.", "errors": { "id": ["User not found."] } }

invalidApiResponse(res, errors)

Sends a 400 response with multiple field errors:

import { asyncValidatorHandler, invalidApiResponse, ApiResponseErrorsValue } from '@qnx/response'

router.post('/register', asyncValidatorHandler(async (req, res) => {
  const { email, password } = req.body

  const errors = ApiResponseErrorsValue.getInstance()
  if (!email) errors.addError('email', 'Email is required.')
  if (!password) errors.addError('password', 'Password is required.')
  if (errors.hasErrors()) return invalidApiResponse(res, errors.getErrors())

  const user = await User.create({ email, password })
  return initializeApiResponse().setData(user).setMessage('Registered.')
}))
{
  "error": "Validation failed",
  "errors": {
    "email": ["Email is required."],
    "password": ["Password is required."]
  }
}

Throw vs Return: Inside asyncValidatorHandler, always throw InvalidValueError / ValidationError — it exits from any depth, needs no res, and TypeScript narrows types after the throw. Reserve invalidApiResponse for code that runs outside the handler (plain Express middleware), where a thrown error has no catcher.


🤖 MCP Server

AI tools for this package are available via the QNX MCP Server.

Endpoint: https://qnx-mcp-server.vercel.app/mcp/response

| Tool | Description | | --- | --- | | get-response-docs | Documentation for handler setup, success responses, validation errors, Zod, unauthenticated, and resource routes | | build-api-response | Show HTTP status code, response body shape, and which @qnx/response function produces it | | transform-to-async-handler | Before/after migration examples for callback, try/catch, promise chain, and next(err) patterns |

Supported clients: Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Continue.dev · Codex CLI · ChatGPT Desktop

HTTP (Streamable HTTP)

Connect directly to the hosted server — no installation required:

{
  "mcpServers": {
    "qnx-response": {
      "url": "https://qnx-mcp-server.vercel.app/mcp/response"
    }
  }
}

stdio (via npx)

Run the MCP server locally using npx. Use this if your client doesn't support HTTP transport or you prefer a local setup:

{
  "mcpServers": {
    "qnx-response": {
      "command": "npx",
      "args": ["-y", "@qnx/mcp", "response"]
    }
  }
}

The response argument scopes the server to only load @qnx/response tools. Omit it to load all qnx tools.

🤝 Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Please make sure to update tests as appropriate.

📄 License

MIT License © 2023-PRESENT Yatendra Kushwaha