undici — The Modern HTTP Client

Fastify's Author Wrote It — Use It for Outbound HTTP

undici — The Modern HTTP Client

undici is Node's fast HTTP/1.1 client and now powers the built-in fetch. Use it for service-to-service calls with pooled connections.

4 min read Level 2/5 #fastify#http#undici
What you'll learn
  • Use built-in fetch powered by undici
  • Use undici.request for streaming responses
  • Pool connections to a downstream service

undici is a low-level HTTP/1.1 client for Node, written by the same team behind Fastify. It is the engine behind the built-in fetch and is the fastest way to make outbound HTTP calls from Node.

fetch — The Easy Path

app.get('/quote', async () => {
  const res = await fetch('https://api.example.com/quote', {
    headers: { authorization: `Bearer ${app.config.UPSTREAM_TOKEN}` },
  })
  if (!res.ok) throw new Error('upstream ' + res.status)
  return res.json()
})

For most calls, fetch is enough. It’s standards-compatible and ships with Node 18+.

undici.request — Streaming

undici.request returns the response body as a stream — handy when forwarding large payloads.

import { request } from 'undici'

app.get('/proxy', async (req, reply) => {
  const { statusCode, headers, body } = await request('https://api.example.com/big.json')
  reply.code(statusCode).headers(headers)
  return body
})

Pool Connections

When you call one host frequently, create a Pool. It reuses TCP connections and avoids the cost of a fresh TLS handshake per request.

import { Pool } from 'undici'
import fp from 'fastify-plugin'

declare module 'fastify' {
  interface FastifyInstance {
    upstream: Pool
  }
}

export default fp(async (app) => {
  const upstream = new Pool('https://api.example.com', { connections: 10 })
  app.decorate('upstream', upstream)
  app.addHook('onClose', async () => upstream.close())
})

A handler now does await app.upstream.request({ path: '/quote', method: 'GET' }) — measurably faster under load than a fresh fetch each time.

Background Jobs With BullMQ →