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

send-http

v3.2.5

Published

Send any value on an HTTP ServerResponse with automatic Content-Type detection — like res.send for Node.js.

Readme

send-http

Last version Coverage Status NPM Status

A straightforward way to send data for http.ServerResponse.

It's like res.send, but:

  • It accepts any kind of value (number, string, object, stream, etc).
  • It leaves a response that already answered alone, rather than throwing at you.
  • It determines Content-Type from the data: the type for values, the first bytes for streams.
  • It optionally sets the status code as second argument.
  • It tears down both ends when a stream fails or the client disconnects.
  • It proxies an HTTP response with proxy, keeping the upstream status and the headers that describe the body.
  • It's small (~110 LOC, one dependency).

Install

$ npm install send-http --save

It requires Node.js >= 24.

Usage

const send = require('send-http')
const http = require('http')
const got = require('got')

http.createServer((req, res) => {
  /* send with no body */
  send(res, 200)

  /* send a string */
  send(res, 200, 'foo')

  /* send an object */
  send(res, 200, { foo: 'bar' })

  /* send a number */
  send(res, 200, 1234)

  /* send a buffer */
  send(res, 200, Buffer.from('hello world'))

  /* send a stream  */
  send(res, 200, got.stream('https.//example.com'))
})

When the body is a stream, the Content-Type is detected from its first bytes via @kikobeats/set-content-type. An already set Content-Type is respected, and an unrecognized payload leaves it unset. proxy is different: when upstream sends Content-Type and it crosses the allowlist, that value is forwarded and the body is not sampled.

When a stream fails before anything reached the client, the response is closed without the error: an upstream that never answered is not a failure of the response itself. Once bytes are on the wire they cannot be retracted, so the response is destroyed with the failure and the client sees a reset rather than a truncated body that looks complete.

Use sendStream to decide what the client gets instead:

const { sendStream } = require('send-http')

http.createServer((req, res) => {
  sendStream(res, 200, got.stream('https://example.com'), {
    onError: (error, res) => {
      res.statusCode = error.code === 'ETIMEDOUT' ? 504 : 502
      res.end()
    }
  })
})

onError runs on the stream, before the response is torn down, so it can still write a reply. Write one and the response is left alone; write nothing and it is closed for you. Once the headers are out there is nothing left to answer with: send returns the response untouched rather than throwing ERR_HTTP_HEADERS_SENT at you from inside a listener, and the client sees the failure as a reset.

send takes the same options as a fourth argument and forwards them when the body is a stream, so send(res, 200, stream, { onError }) is sendStream(res, 200, stream, { onError }).

proxy

Relaying an HTTP response is not the same as sending a stream: status and body-descriptor headers come from the upstream once it answers.

const { proxy } = require('send-http')

http.createServer((req, res) => {
  proxy(res, got.stream('https://example.com/video.mp4'), {
    onError: (error, res) => send(res, 502, { error: error.message })
  })
})

onError follows the same rules as sendStream above.

headers is an allowlist of lowercase names that may cross; nothing else does. It defaults to STREAM_ALLOWED_HEADERS:

const { STREAM_ALLOWED_HEADERS } = require('send-http')

proxy(res, upstream, { headers: [...STREAM_ALLOWED_HEADERS, 'etag'] })

content-length is not in the default list: upstreams often declare a wrong length while still sending the full body, so the proxied response uses chunked transfer instead. Re-adding it takes that back, and a length the upstream got wrong truncates the response.

proxy accepts either a stream that emits response later, or an IncomingMessage that already answered:

proxy(res, got.stream(url))
http.get(url, upstream => proxy(res, upstream))

got.stream decompresses by default. Decoding hands over a different representation of the body, so content-encoding, content-length, content-range and etag from that hop are dropped rather than mislabel the bytes being piped. That holds however they got on the list, the default one or a custom one. Pass { decoded: false } to relay the compressed representation instead (same as piping an IncomingMessage), and all four cross with the bytes they describe.

When content-type crosses, the first byte reaches the client as soon as the upstream produces it. If it does not cross, Content-Type is sniffed from the first bytes like sendStream.

Teardown matches streams (see above): the upstream is destroyed when the client leaves, including before the upstream has answered.

create

Customizes the write of a buffered body (streams go through sendStream, and an empty body ends the response without a write to customize). The hook runs after the Content-Type and Content-Length are set, and receives the body as a Buffer whatever it was given:

const send = require('send-http').create((res, data) => {
  if (data.length > 6291456) {
    throw new Error('Payload size is over 6mb')
  }
  return res.end(data)
})

isStream

The predicate behind the dispatch, exported for reusing the same rule:

const { isStream } = require('send-http')

isStream(got.stream('https://example.com')) // => true
isStream({}) // => false

canAnswer

The guard behind the no-op: false once the headers went out or the response ended, which is when writing to it would throw. Use it to skip work whose only purpose was the answer:

const { canAnswer } = require('send-http')

if (canAnswer(res)) send(res, 500, { error: 'upstream failed' })

send already checks it, so an unconditional call is safe too: it returns the response untouched rather than throwing.

License

send-http © Kiko Beats, released under the MIT License. Authored and maintained by Kiko Beats with help from contributors.

kikobeats.com · GitHub Kiko Beats · X @Kikobeats