Skip to main content

http-proxy-middleware vs node-http-proxy vs fastify-http-proxy: Reverse Proxy in Node.js (2026)

·PkgPulse Team

TL;DR

http-proxy-middleware is the Express/Connect proxy middleware — used by Create React App and Vite for dev server proxying, supports path rewriting and WebSocket forwarding. node-http-proxy (http-proxy) is the foundational library underneath — low-level, handles raw HTTP/HTTPS/WS proxying, maximum control. @fastify/http-proxy is Fastify's built-in proxy plugin — uses undici for high-performance proxying with Fastify's hook system. In 2026: http-proxy-middleware for Express dev-server proxying, node-http-proxy for custom proxy servers, @fastify/http-proxy for Fastify API gateways.

Key Takeaways

  • http-proxy-middleware: ~8M weekly downloads — Express middleware, powers CRA/Vite dev proxy
  • node-http-proxy (http-proxy): ~5M weekly downloads — foundational, low-level proxy engine
  • @fastify/http-proxy: ~200K weekly downloads — undici-based, Fastify hooks, high performance
  • http-proxy-middleware wraps node-http-proxy — adds middleware API, path rewriting, logging
  • @fastify/http-proxy uses undici (not http-proxy) — different foundation, faster for Fastify
  • Common use cases: dev server API proxy, microservice gateway, BFF (backend for frontend)

Why Proxy in Node.js?

Use case 1: Development proxy (avoid CORS):
  Frontend (localhost:3000) → Proxy → Backend API (localhost:8080)
  React/Vite dev server proxies /api/* to backend

Use case 2: API gateway:
  Client → Gateway (Node.js) → Service A (/api/packages)
                              → Service B (/api/users)
                              → Service C (/api/billing)

Use case 3: BFF (Backend for Frontend):
  Mobile app → BFF → combines data from multiple services
  Web app → BFF → aggregates + transforms responses

Use case 4: Load balancing / A-B testing:
  Client → Proxy → 90% → Production service
                 → 10% → Canary service

http-proxy-middleware

http-proxy-middleware — Express/Connect proxy:

Basic setup

import express from "express"
import { createProxyMiddleware } from "http-proxy-middleware"

const app = express()

// Proxy /api/* requests to backend:
app.use("/api", createProxyMiddleware({
  target: "http://localhost:8080",
  changeOrigin: true,
}))

// Request: GET http://localhost:3000/api/packages/react
// Proxied: GET http://localhost:8080/api/packages/react

app.listen(3000)

Path rewriting

app.use("/api", createProxyMiddleware({
  target: "http://localhost:8080",
  changeOrigin: true,
  pathRewrite: {
    "^/api/v2": "/v2",          // /api/v2/packages → /v2/packages
    "^/api": "",                 // /api/packages → /packages
  },
}))

Multiple targets

// Route different paths to different services:
app.use("/api/packages", createProxyMiddleware({
  target: "http://package-service:3001",
  changeOrigin: true,
  pathRewrite: { "^/api/packages": "/packages" },
}))

app.use("/api/users", createProxyMiddleware({
  target: "http://user-service:3002",
  changeOrigin: true,
  pathRewrite: { "^/api/users": "/users" },
}))

app.use("/api/billing", createProxyMiddleware({
  target: "http://billing-service:3003",
  changeOrigin: true,
}))

WebSocket proxying

app.use("/ws", createProxyMiddleware({
  target: "http://localhost:8080",
  ws: true,          // Enable WebSocket proxying
  changeOrigin: true,
}))

Request/response modification

app.use("/api", createProxyMiddleware({
  target: "http://localhost:8080",
  changeOrigin: true,

  on: {
    // Modify outgoing request:
    proxyReq(proxyReq, req, res) {
      proxyReq.setHeader("X-Forwarded-For", req.ip)
      proxyReq.setHeader("X-Request-ID", crypto.randomUUID())

      // Add auth header:
      proxyReq.setHeader("Authorization", `Bearer ${getServiceToken()}`)
    },

    // Modify incoming response:
    proxyRes(proxyRes, req, res) {
      proxyRes.headers["x-proxy"] = "pkgpulse-gateway"
    },

    // Handle proxy errors:
    error(err, req, res) {
      console.error("Proxy error:", err.message)
      res.writeHead(502, { "Content-Type": "application/json" })
      res.end(JSON.stringify({ error: "Bad gateway", upstream: err.message }))
    },
  },
}))

Vite dev server proxy (built-in)

// vite.config.ts — uses http-proxy-middleware under the hood:
import { defineConfig } from "vite"

export default defineConfig({
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
      "/ws": {
        target: "ws://localhost:8080",
        ws: true,
      },
    },
  },
})

node-http-proxy

node-http-proxy — low-level proxy:

Basic proxy server

import httpProxy from "http-proxy"
import http from "node:http"

// Create a proxy instance:
const proxy = httpProxy.createProxyServer({
  target: "http://localhost:8080",
  changeOrigin: true,
  ws: true,
})

// Handle proxy errors:
proxy.on("error", (err, req, res) => {
  console.error("Proxy error:", err.message)
  if (res.writeHead) {
    res.writeHead(502, { "Content-Type": "application/json" })
    res.end(JSON.stringify({ error: "Bad gateway" }))
  }
})

// Create HTTP server:
const server = http.createServer((req, res) => {
  proxy.web(req, res)
})

// Handle WebSocket upgrade:
server.on("upgrade", (req, socket, head) => {
  proxy.ws(req, socket, head)
})

server.listen(3000)

Dynamic routing

import httpProxy from "http-proxy"
import http from "node:http"

const proxy = httpProxy.createProxyServer()

const routes: Record<string, string> = {
  "/api/packages": "http://package-service:3001",
  "/api/users": "http://user-service:3002",
  "/api/billing": "http://billing-service:3003",
}

const server = http.createServer((req, res) => {
  // Find matching route:
  const route = Object.keys(routes).find(prefix => req.url?.startsWith(prefix))

  if (route) {
    proxy.web(req, res, { target: routes[route] })
  } else {
    res.writeHead(404)
    res.end("Not found")
  }
})

server.listen(3000)

Load balancing

import httpProxy from "http-proxy"
import http from "node:http"

const targets = [
  "http://app-1:3000",
  "http://app-2:3000",
  "http://app-3:3000",
]

let current = 0

const proxy = httpProxy.createProxyServer()

const server = http.createServer((req, res) => {
  // Round-robin:
  const target = targets[current % targets.length]
  current++

  proxy.web(req, res, { target })
})

server.listen(80)

SSL termination

import httpProxy from "http-proxy"
import https from "node:https"
import fs from "node:fs"

const proxy = httpProxy.createProxyServer({
  target: "http://localhost:8080",  // Backend is HTTP
  changeOrigin: true,
})

// HTTPS frontend → HTTP backend:
const server = https.createServer({
  key: fs.readFileSync("certs/key.pem"),
  cert: fs.readFileSync("certs/cert.pem"),
}, (req, res) => {
  proxy.web(req, res)
})

server.listen(443)

@fastify/http-proxy

@fastify/http-proxy — Fastify proxy plugin:

Basic setup

import Fastify from "fastify"
import proxy from "@fastify/http-proxy"

const fastify = Fastify({ logger: true })

// Proxy /api/* to backend:
await fastify.register(proxy, {
  upstream: "http://localhost:8080",
  prefix: "/api",
  rewritePrefix: "/",     // /api/packages → /packages
})

await fastify.listen({ port: 3000 })

Multiple upstreams (API gateway)

import Fastify from "fastify"
import proxy from "@fastify/http-proxy"

const fastify = Fastify({ logger: true })

// Package service:
await fastify.register(proxy, {
  upstream: "http://package-service:3001",
  prefix: "/api/packages",
  rewritePrefix: "/packages",
})

// User service:
await fastify.register(proxy, {
  upstream: "http://user-service:3002",
  prefix: "/api/users",
  rewritePrefix: "/users",
})

// Billing service:
await fastify.register(proxy, {
  upstream: "http://billing-service:3003",
  prefix: "/api/billing",
  rewritePrefix: "/billing",
})

await fastify.listen({ port: 3000 })

With Fastify hooks (auth, logging)

await fastify.register(proxy, {
  upstream: "http://package-service:3001",
  prefix: "/api/packages",

  // Pre-handler — runs before proxying:
  preHandler: async (request, reply) => {
    // Authenticate:
    const token = request.headers.authorization
    if (!token) {
      return reply.code(401).send({ error: "Unauthorized" })
    }

    // Add request ID:
    request.headers["x-request-id"] = crypto.randomUUID()
  },

  // Modify reply headers:
  replyOptions: {
    onResponse(request, reply, res) {
      reply.header("x-proxy", "pkgpulse-gateway")
      reply.send(res)
    },
  },
})

WebSocket proxying

await fastify.register(proxy, {
  upstream: "http://localhost:8080",
  prefix: "/ws",
  websocket: true,   // Enable WebSocket proxying
})

Feature Comparison

Featurehttp-proxy-middlewarenode-http-proxy@fastify/http-proxy
FrameworkExpress/ConnectAnyFastify only
Underlying enginehttp-proxySelfundici
Path rewritingManual✅ (rewritePrefix)
WebSocket proxy
Request modification✅ (onProxyReq)✅ (proxyReq event)✅ (preHandler)
Load balancingManual❌ (use with fastify-reply-from)
SSL termination
PerformanceGoodGoodBest (undici)
Weekly downloads~8M~5M~200K

When to Use Each

Choose http-proxy-middleware if:

  • Express application — industry standard proxy middleware
  • Dev server proxying (avoiding CORS during development)
  • Simple API gateway with path rewriting
  • Using Vite/CRA dev proxy (uses this under the hood)

Choose node-http-proxy if:

  • Building a standalone proxy server (not inside an app framework)
  • Need SSL termination, load balancing, or custom routing logic
  • Want maximum control over the proxy behavior
  • Implementing A/B testing or canary deployments at the proxy level

Choose @fastify/http-proxy if:

  • Building an API gateway with Fastify
  • Need Fastify's hook system for auth, logging, rate limiting
  • Want undici's performance for high-throughput proxying
  • Microservice architecture with Fastify

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on http-proxy-middleware v3.x, http-proxy v1.x, and @fastify/http-proxy v10.x.

Compare proxy, API gateway, and networking packages on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.