Skip to content

fetch-proxy Overview

The fetch-proxy package creates a fetch-compatible function that forwards requests to another server. It handles URL rewriting, header forwarding, streaming request/response bodies, and automatic cookie rewriting so proxied responses work correctly on the client.

What is an HTTP Proxy?

An HTTP proxy is a server that sits between a client and another server, forwarding requests on the client's behalf. The client sends a request to the proxy, the proxy sends it to the real server (called the target or upstream), and the proxy sends the response back to the client.

Client (browser)
      |
      | Request: GET /api/users
      v
Your Server (proxy)
      |
      | Forwards to: GET http://api-service:8080/api/users
      v
Backend API Service
      |
      | Response: 200 OK, [...users]
      v
Your Server (proxy)
      |
      | Forwards response back
      v
Client (browser)

The client may not even know a proxy is involved -- from its perspective, it is talking to your server directly.

When to Use a Proxy

API Gateway

Your frontend server proxies API requests to one or more backend services. This lets you expose a single domain to clients while routing to different services internally:

  • /api/users goes to the Users service
  • /api/orders goes to the Orders service
  • /api/auth goes to the Auth service

Development Proxy

During development, you might run your frontend on one port and your API on another. A proxy lets your frontend make requests to /api/... without cross-origin issues.

Microservices

In a microservices architecture, a gateway service accepts all incoming requests and fans them out to the appropriate internal services based on the URL.

Key Features

Fetch-Compatible Interface

The proxy function has the same signature as the global fetch function. You can pass it a Request object or a URL string, just like you would with fetch():

ts
import { createFetchProxy } from 'remix/fetch-proxy'

let proxy = createFetchProxy('http://api.internal:8080')
let response = await proxy(request)

When the target server sets cookies, the Domain and Path attributes are automatically rewritten so they work on your domain instead of the target's domain. This is critical for authentication cookies to function correctly through the proxy.

Streaming

Request and response bodies are streamed, not buffered. Large uploads and downloads pass through efficiently without consuming memory on the proxy server.

Forwarded Headers

Optionally add X-Forwarded-Proto and X-Forwarded-Host headers so the target server knows the original client protocol and hostname.

Quick Example

Proxy all requests under /api/ to an internal backend service:

ts
import * as http from 'node:http'
import { createRouter } from 'remix/fetch-router'
import { createRequestListener } from 'remix/node-fetch-server'
import { createFetchProxy } from 'remix/fetch-proxy'

let apiProxy = createFetchProxy('http://api.internal:8080')

let router = createRouter()

router.get('/', () => {
  return new Response('Frontend')
})

// Forward all /api/* requests to the backend
router.get('/api/*path', ({ request }) => {
  return apiProxy(request)
})

router.post('/api/*path', ({ request }) => {
  return apiProxy(request)
})

let server = http.createServer(createRequestListener(router.fetch))
server.listen(3000, () => {
  console.log('Server running at http://localhost:3000')
})

A request to http://localhost:3000/api/users is forwarded to http://api.internal:8080/api/users. The response (including any cookies) is sent back to the client with appropriate rewrites.

URL Rewriting

The proxy prepends the target's origin and pathname to the incoming request's path. If you create a proxy with createFetchProxy('http://api.internal:8080/v2'), a request to /users becomes http://api.internal:8080/v2/users.

How it Fits in the Remix Ecosystem

The proxy function is just a fetch-compatible function, so it integrates naturally with the router -- a route handler simply calls the proxy and returns the result.

Next Steps

Released under the MIT License.