node-fetch-server
The node-fetch-server package bridges the gap between the Fetch API and Node.js. It converts a standard Fetch handler (a function that takes a Request and returns a Response) into a Node.js request listener that works with http.createServer(), https.createServer(), and http2.createServer().
This is how you run a Remix application on Node.js.
Installation
# Via the meta-package
npm install remix
# Or individually
npm install @remix-run/node-fetch-serverImports
import {
createRequestListener,
createRequest,
createHeaders,
sendResponse,
} from 'remix/node-fetch-server'
// Types
import type {
RequestListenerOptions,
RequestOptions,
FetchHandler,
ErrorHandler,
ClientAddress,
} from 'remix/node-fetch-server'createRequestListener(handler, options?)
Wraps a Fetch handler in a Node.js request listener. This is the primary function you use to serve a Remix application.
import * as http from 'node:http'
import { createRequestListener } from 'remix/node-fetch-server'
async function handler(request: Request): Promise<Response> {
return new Response('Hello, world!')
}
let server = http.createServer(createRequestListener(handler))
server.listen(3000)With a Remix Router
The most common usage is to pass a router's fetch method:
import * as http from 'node:http'
import { createRouter } from 'remix/fetch-router'
import { createRequestListener } from 'remix/node-fetch-server'
let router = createRouter()
router.get('/', () => new Response('Home'))
router.get('/about', () => new Response('About'))
let server = http.createServer(createRequestListener(router.fetch))
server.listen(3000, () => {
console.log('Listening on http://localhost:3000')
})Parameters
| Parameter | Type | Description |
|---|---|---|
handler | FetchHandler | A function that takes a Request and ClientAddress and returns a Response. |
options | RequestListenerOptions | Optional configuration. |
Returns
An http.RequestListener function that can be passed to http.createServer().
RequestListenerOptions
| Option | Type | Default | Description |
|---|---|---|---|
host | string | From Host header | Overrides the host portion of the request URL. |
protocol | string | From connection | Overrides the protocol of the request URL. |
onError | ErrorHandler | Logs + 500 | Custom error handler for when the fetch handler throws. |
Custom Host
Use the host option when your server is behind a reverse proxy and you want request URLs to reflect the public hostname:
createRequestListener(handler, {
host: process.env.HOST, // e.g. "example.com"
})Custom Protocol
Force all request URLs to use HTTPS (useful behind a TLS-terminating proxy):
createRequestListener(handler, {
protocol: 'https:',
})Custom Error Handler
Handle errors thrown by the fetch handler:
createRequestListener(handler, {
onError(error) {
console.error('Request failed:', error)
return new Response('Something went wrong', { status: 500 })
},
})If the error handler itself throws or returns undefined, a default 500 Internal Server Error response is sent.
FetchHandler
The type for functions that handle Fetch API requests.
interface FetchHandler {
(request: Request, client: ClientAddress): Response | Promise<Response>
}The client parameter provides information about the remote client that sent the request.
ClientAddress
Information about the client that sent a request.
interface ClientAddress {
address: string // IP address (e.g. "127.0.0.1")
family: 'IPv4' | 'IPv6'
port: number // Remote port
}You can use the client address for logging, rate limiting, or IP-based access control:
let handler: FetchHandler = (request, client) => {
console.log(`Request from ${client.address}:${client.port}`)
return new Response('OK')
}ErrorHandler
A function that handles errors thrown during request processing.
interface ErrorHandler {
(error: unknown): void | Response | Promise<void | Response>
}If it returns a Response, that response is sent to the client. If it returns void or undefined, a default 500 response is sent.
createRequest(req, res, options?)
Creates a Fetch API Request from a Node.js IncomingMessage and ServerResponse pair. This is a lower-level function --- createRequestListener() calls it internally.
import * as http from 'node:http'
import { createRequest, sendResponse } from 'remix/node-fetch-server'
let server = http.createServer(async (req, res) => {
let request = createRequest(req, res)
// Use the standard Request object
console.log(request.method, request.url)
let response = new Response('Hello')
await sendResponse(res, response)
})Parameters
| Parameter | Type | Description |
|---|---|---|
req | http.IncomingMessage | http2.Http2ServerRequest | The Node.js incoming request. |
res | http.ServerResponse | http2.Http2ServerResponse | The Node.js server response. |
options | RequestOptions | Optional host and protocol overrides. |
Streaming Request Bodies
For requests with bodies (POST, PUT, etc.), the request body is provided as a ReadableStream that streams data from the Node.js request. The body is not buffered in memory.
Abort Signals
The created Request has an AbortSignal that fires when the client disconnects (the response's close event fires without a preceding finish event). This allows handlers to stop work early when the client goes away.
createHeaders(req)
Creates a Fetch API Headers object from a Node.js incoming message. Handles multi-value headers correctly and filters out HTTP/2 pseudo-headers.
import { createHeaders } from 'remix/node-fetch-server'
let headers = createHeaders(req)
console.log(headers.get('Content-Type'))sendResponse(res, response)
Sends a Fetch API Response through a Node.js ServerResponse. Handles streaming response bodies, HTTP/2 compatibility, and multiple Set-Cookie headers correctly.
import { sendResponse } from 'remix/node-fetch-server'
let response = new Response('Hello', {
headers: { 'Content-Type': 'text/plain' },
})
await sendResponse(res, response)Streaming
If the response has a body that is a ReadableStream, sendResponse streams it chunk by chunk to the client. It respects Node.js backpressure --- if the socket buffer is full, it waits for a drain event before writing more data.
HTTP/2 Support
sendResponse automatically detects HTTP/2 responses and omits the status text (which HTTP/2 does not support), avoiding Node.js warnings.
HTTPS Support
Use createRequestListener() with https.createServer() for HTTPS:
import * as https from 'node:https'
import * as fs from 'node:fs'
import { createRequestListener } from 'remix/node-fetch-server'
let server = https.createServer(
{
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
},
createRequestListener(handler)
)
server.listen(443)The request URL protocol is automatically detected as https: when using https.createServer().
HTTP/2 Support
Works with both http2.createServer() and http2.createSecureServer():
import * as http2 from 'node:http2'
import { createRequestListener } from 'remix/node-fetch-server'
let server = http2.createSecureServer(
{
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
},
createRequestListener(handler)
)
server.listen(443)Complete Example
import * as http from 'node:http'
import { createRouter } from 'remix/fetch-router'
import { route, form } from 'remix/fetch-router/routes'
import { createRequestListener } from 'remix/node-fetch-server'
import { logger } from 'remix/logger-middleware'
import { compression } from 'remix/compression-middleware'
// Define routes
let routes = route({
home: '/',
contact: form('contact'),
health: '/health',
})
// Create router with middleware
let router = createRouter({
middleware: [logger(), compression()],
})
// Register handlers
router.get(routes.home, () => {
return new Response('<h1>Welcome</h1>', {
headers: { 'Content-Type': 'text/html' },
})
})
router.get(routes.health, () => {
return Response.json({ status: 'ok' })
})
// Start the server
let port = Number(process.env.PORT) || 3000
let server = http.createServer(
createRequestListener(router.fetch, {
onError(error) {
console.error('Unhandled error:', error)
return new Response('Internal Server Error', { status: 500 })
},
})
)
server.listen(port, () => {
console.log(`Server listening on http://localhost:${port}`)
})Related Packages
- fetch-router --- The router that produces the
FetchHandleryou pass tocreateRequestListener(). - fetch-proxy --- Proxy requests to another server from within a fetch handler.
Related Guides
- Deployment --- Deploy your Remix application to various platforms.