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.
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.