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

formatted-response

v2.1.0

Published

Create formatted, consistently-shaped JSON responses — synchronously or as promises. Zero runtime dependencies.

Readme

formatted-response

Create formatted, consistently-shaped JSON responses for your application — synchronously or as promises. Zero runtime dependencies. Written in TypeScript, published as dual ESM + CommonJS.

Features

  • Zero runtime dependencies
  • Fully typed — the success/error/warning/... methods infer whether they return a value or a Promise of one, from your config
  • Works with require(), import, and TypeScript out of the box
  • Node.js ≥ 20.19

Installation

npm install formatted-response
# or
yarn add formatted-response

Usage

ESM

import FormattedResponse from 'formatted-response'

const response = new FormattedResponse()

// response.promise defaults to true, so this returns a Promise
const payload = await response.success('MY.DATA', { myData: 'My data!' })
// => { status: 200, message: 'MY.DATA.OK', type: 'success', data: { myData: 'My data!' } }

CommonJS

const FormattedResponse = require('formatted-response')

const response = new FormattedResponse({ promise: false })

console.log(response.success('MY.DATA', { myData: 'My data!' }))
// => { status: 200, message: 'MY.DATA.OK', type: 'success', data: { myData: 'My data!' } }

TypeScript

The return type of every method is inferred from promise in the config you pass the constructor — no casts needed for the common case:

import FormattedResponse from 'formatted-response'

const sync = new FormattedResponse({ promise: false })
sync.success('M') // ResponsePayload — synchronous

const async_ = new FormattedResponse() // promise: true is the default
async_.success('M') // Promise<ResponsePayload>

Inference works from a config object literal. If your config comes from a variable typed as FormattedResponseOptions, TypeScript can no longer know which branch you're on at compile time, and the return type falls back to ResponsePayload | Promise<ResponsePayload>. Three ways around that, in order of preference:

// 1. Best — a runtime-guaranteed factory
const R = FormattedResponse.sync(runtimeConfig)   // always ResponsePayload
const A = FormattedResponse.async(runtimeConfig)  // always Promise<ResponsePayload>

// 2. `as const` on the literal
const cfg = { promise: false } as const
const R = new FormattedResponse(cfg)

// 3. The universal escape hatch — works whether or not it's actually a Promise
const r = await response.success('SAVED', { id: 1 })

Both new FormattedResponse(config) and FormattedResponse(config) (without new) work, for compatibility with 1.x — instanceof checks still succeed either way.

API

Every method's last argument is an optional promise: boolean, which overrides the instance's config.promise for that one call.

| Method | Adds to message | status | type | |---|---|---|---| | response(message, data, type, status, promise?) | (nothing — full control) | as given | as given | | get(message, data?, promise?) | config.get.ok | config.ok.status | (none) | | success(message, data?, promise?) | config.ok.suffix | config.ok.status | config.types.ok | | error(message, error?, promise?) | config.ko.suffix | config.ko.status | config.types.ko | | warning(message, error?, promise?) | config.warn.suffix | config.warn.status | config.types.warn | | notFound(message, error?, promise?) | config.notFound.suffix | config.notFound.status | config.types.notFound |

const response = new FormattedResponse({ promise: false })

response.success('MY.DATA', { myData: 'My data!' })
// => { status: 200, message: 'MY.DATA.OK', type: 'success', data: { myData: 'My data!' } }

response.get('MY.DATA', { myData: 'My data!' })
// => { status: 200, message: 'MY.DATA.GET.OK', data: { myData: 'My data!' } }

response.error('MY.DATA', new Error('My Error!'))
// => { status: 400, message: 'MY.DATA.KO', type: 'error' }
// (the `error` message itself is only included when `debug: true` — see Configuration)

response.warning('MY.DATA', new Error('My Error!'))
// => { status: 403, message: 'MY.DATA.WARN', type: 'warning' }

response.notFound('MY.DATA', new Error('My Error!'))
// => { status: 404, message: 'MY.DATA.ERROR', type: 'error' }

response.response('MY.DATA', { myData: 'My data!' }, 'my-type', 200)
// => { status: 200, message: 'MY.DATA', type: 'my-type', data: { myData: 'My data!' } }

[!WARNING] debug: false hides the error's message, but not its errors property. If the error you pass has one, it is always copied into the response, as data.errors (or top-level errors when debug: true):

const err = Object.assign(new Error('Validation failed'), {
  errors: [{ path: 'email', value: '[email protected]' }]
})
response.error('USER.CREATE', err)
// => { status: 400, message: 'USER.CREATE.KO', type: 'error', data: { errors: [{ path: 'email', value: '[email protected]' }] } }

ORM and validation libraries (Sequelize, Mongoose, …) put internal details there, such as rejected values, model and column names. Don't pass their errors directly to a response sent to clients. Map them first to what the client should see:

response.error('USER.CREATE', { errors: err.errors.map(e => ({ field: e.path })) })

data is nested under a data key automatically, unless it already has a data or error key of its own, in which case it's spread at the top level:

response.success('X', { count: 1 })              // => {..., data: { count: 1 }}
response.success('X', { data: { count: 1 } })     // => {..., data: { count: 1 }}  (same result)
response.response('X', { data: { a: 1 }, extra: 2 }, 't', 200)
// => { status: 200, message: 'X', type: 't', data: { a: 1 }, extra: 2 }

Any HTTP status

status(code, message, data?, promise?) sends any code from 100 to 599. Below 400 the third argument is data. From 400 up it's parsed as an error, exactly like error(), so debug and the errors rules above apply. The suffix is the code's name from the IANA registry, and the type is types.ok below 400 and types.ko from 400 up:

response.status(201, 'USER', { id: 1 })
// => { status: 201, message: 'USER.CREATED', type: 'success', data: { id: 1 } }

response.status(FormattedResponse.HttpStatus.CONFLICT, 'USER', err)
// => { status: 409, message: 'USER.CONFLICT', type: 'error' }

A code outside the registry (e.g. 299) gets no suffix. A code outside 100–599 throws a RangeError.

The common codes also have named methods, with the same arguments as success() or error():

| Takes data | Takes an error | |---|---| | created (201), accepted (202), noContent (204) | badRequest (400), unauthorized (401), forbidden (403), conflict (409), unprocessableContent (422), tooManyRequests (429), internalServerError (500), serviceUnavailable (503) |

FormattedResponse.HttpStatus maps every registry name to its code (HttpStatus.CREATED === 201). error(), warning() and notFound() are unchanged: they still use .KO, .WARN and .ERROR.

Configuration

All defaults live in FormattedResponse.defaults (or the deprecated formatted-response/config subpath). Pass a partial override to the constructor — it's deep-merged over the defaults.

const response = new FormattedResponse({
  debug: true,
  promise: false,
  types: { ok: 'alert-success', ko: 'alert-danger', warn: 'alert-warning', notFound: 'alert-notFound' },
  get: { ok: '.GET.SUCCESS', ko: '.GET.ERROR', warn: '.GET.WARNING' },
  ok: { status: 200, suffix: '.SUCCESS' },
  ko: { status: 400, suffix: '.ERROR' },
  warn: { status: 403, suffix: '.WARNING' },
  notFound: { status: 404, suffix: '.ERROR' },
  dateFields: ['created_at', 'created', 'updated_at', 'updated']
})

| Key | Default | Meaning | |---|---|---| | debug | false | When true, error/warning/notFound include the underlying Error's message under an error key | | promise | true | Default return mode for every method — synchronous value vs Promise | | types.{ok,ko,warn,notFound} | success / error / warning / error | The type field emitted by success/error/warning/notFound | | get.ok | .GET.OK | Suffix appended to message by get() | | ok.{status,suffix} | 200 / .OK | Used by success() | | ko.{status,suffix} | 400 / .KO | Used by error() | | warn.{status,suffix} | 403 / .WARN | Used by warning() | | notFound.{status,suffix} | 404 / .ERROR | Used by notFound() | | dateFields | ['created_at', 'created', 'updated_at', 'updated'] | Keys in data whose value, if a real Date, is converted to an ISO string | | statuses | (none) | Per-code overrides for status() and the named status methods: { 409: { suffix: '.TAKEN', type: 'warning' } } |

get.ko, get.warn, and auth.* are accepted in the config shape for backwards compatibility but are not read by any method — reserved for future use.

Types

The package ships full TypeScript types at the package root, plus at the formatted-response/types subpath for advanced usage:

import type {
  FormattedResponseConfig,   // the fully-resolved config
  FormattedResponseOptions,  // partial config accepted by the constructor
  BaseResponse, TypedResponse, ErrorResponse,  // response payload shapes
  ErrorInput,                // what error()/warning()/notFound() accept
  StatusResponse,            // what status() returns
  StatusOverride,            // one entry of config.statuses
  HttpStatusCode, HttpStatusName,  // registry codes and names
  Resolved                   // the sync/promise resolution helper
} from 'formatted-response/types'

Migration from 1.x

Breaking changes in 2.0.0:

  1. Node ≥ 20.19 is required.
  2. ObjDeepMerge, _isObject, _isDate, _promise, and _parseErrors are no longer public on the prototype. They were internal helpers (four of them _-prefixed already); if you depended on them, they're easy to reimplement — see src/internal/merge.ts.
  3. data passed into success/error/etc. is no longer mutated. In 1.x, converting a dateFields value to an ISO string wrote back into your own object; it no longer does.
  4. Non-plain objects at keys not listed in dateFields (Date, Map, Set, Buffer, class instances) are now preserved by reference instead of being silently flattened to {} — this fixes a crash on Buffer values and a silent data-loss bug on Date/Map values, but it does change output for anyone who was relying on the old flattening behavior.
  5. config.dateFields is now copied per instance instead of being shared by reference with the defaults — mutating it on one instance no longer poisons every other instance in the process.
  6. require('formatted-response/config') still works but is deprecated; use FormattedResponse.defaults instead.
  7. Only the documented entry points (., ./config, ./types) are importable — deep paths like formatted-response/src/response no longer resolve.

Everything else — the method signatures, the default config values, the message/status/type shapes — is unchanged.

Changelog

See CHANGELOG.md.

Contributing

Issues and PRs welcome at github.com/Kareylo/formatted-response.

npm install   # or: yarn install
npm run verify   # lint + typecheck + test + build + package checks

License

MIT © Corentin Nazé