Documentation Index
Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt
Use this file to discover all available pages before exploring further.
Build Node.js servers with web-standard Fetch API primitives. node-fetch-server converts Node’s HTTP server interfaces into Request/Response flows that match modern runtimes.
Features
- Web Standards - Standard
Request and Response APIs
- Drop-in Integration - Works with
node:http and node:https modules
- Streaming Support - Response support with
ReadableStream
- Custom Hostname - Configuration for deployment flexibility
- Client Info - Access to client connection info (IP address, port)
- TypeScript - Full TypeScript support with type definitions
Installation
Quick Start
Basic Server
Here’s a complete working example with a simple in-memory data store:
import * as http from 'node:http'
import { createRequestListener } from 'remix/node-fetch-server'
// Example: Simple in-memory user storage
let users = new Map([
['1', { id: '1', name: 'Alice', email: 'alice@example.com' }],
['2', { id: '2', name: 'Bob', email: 'bob@example.com' }],
])
async function handler(request: Request) {
let url = new URL(request.url)
// GET / - Home page
if (url.pathname === '/' && request.method === 'GET') {
return new Response('Welcome to the User API! Try GET /api/users')
}
// GET /api/users - List all users
if (url.pathname === '/api/users' && request.method === 'GET') {
return Response.json(Array.from(users.values()))
}
// GET /api/users/:id - Get specific user
let userMatch = url.pathname.match(/^\/api\/users\/(\w+)$/)
if (userMatch && request.method === 'GET') {
let user = users.get(userMatch[1])
if (user) {
return Response.json(user)
}
return new Response('User not found', { status: 404 })
}
return new Response('Not Found', { status: 404 })
}
// Create a standard Node.js server
let server = http.createServer(createRequestListener(handler))
server.listen(3000, () => {
console.log('Server running at http://localhost:3000')
})
Working with Request Data
Handle different types of request data using standard web APIs:
async function handler(request: Request) {
let url = new URL(request.url)
// Handle JSON data
if (request.method === 'POST' && url.pathname === '/api/users') {
try {
let userData = await request.json()
// Validate required fields
if (!userData.name || !userData.email) {
return Response.json(
{ error: 'Name and email are required' },
{ status: 400 }
)
}
// Create user (your implementation)
let newUser = {
id: Date.now().toString(),
...userData,
}
return Response.json(newUser, { status: 201 })
} catch (error) {
return Response.json({ error: 'Invalid JSON' }, { status: 400 })
}
}
// Handle URL search params
if (url.pathname === '/api/search') {
let query = url.searchParams.get('q')
let limit = parseInt(url.searchParams.get('limit') || '10')
return Response.json({
query,
limit,
results: [], // Your search results here
})
}
return new Response('Not Found', { status: 404 })
}
Streaming Responses
Take advantage of web-standard streaming with ReadableStream:
async function handler(request: Request) {
if (request.url.endsWith('/stream')) {
// Create a streaming response
let stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 5; i++) {
controller.enqueue(new TextEncoder().encode(`Chunk ${i}\n`))
await new Promise((resolve) => setTimeout(resolve, 1000))
}
controller.close()
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/plain' },
})
}
return new Response('Not Found', { status: 404 })
}
Custom Hostname Configuration
Configure custom hostnames for deployment on VPS or custom environments:
import * as http from 'node:http'
import { createRequestListener } from 'remix/node-fetch-server'
// Use a custom hostname (e.g., from environment variable)
let hostname = process.env.HOST || 'api.example.com'
async function handler(request: Request) {
// request.url will now use your custom hostname
console.log(request.url) // https://api.example.com/path
return Response.json({
message: 'Hello from custom domain!',
url: request.url,
})
}
let server = http.createServer(
createRequestListener(handler, { host: hostname })
)
server.listen(3000)
Get client connection details (IP address, port) for logging or security:
import { type FetchHandler } from 'remix/node-fetch-server'
let handler: FetchHandler = async (request, client) => {
// Log client information
console.log(`Request from ${client.address}:${client.port}`)
// Use for rate limiting, geolocation, etc.
if (isRateLimited(client.address)) {
return new Response('Too Many Requests', { status: 429 })
}
return Response.json({
message: 'Hello!',
yourIp: client.address,
})
}
HTTPS Support
Use with Node.js HTTPS module for secure connections:
import * as https from 'node:https'
import * as fs from 'node:fs'
import { createRequestListener } from 'remix/node-fetch-server'
let options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
}
let server = https.createServer(options, createRequestListener(handler))
server.listen(443, () => {
console.log('HTTPS Server running on port 443')
})
API Reference
createRequestListener(handler, options?)
Creates a request listener function that can be used with http.createServer() or https.createServer().
Parameters:
handler: FetchHandler - Function that handles the request and returns a Response
options?: RequestListenerOptions - Optional configuration
host?: string - Custom hostname to use in request URLs
Returns: (req: IncomingMessage, res: ServerResponse) => Promise<void>
FetchHandler
A function that handles an incoming request and returns a response.
interface FetchHandler {
(request: Request, client: ClientAddress): Response | Promise<Response>
}
ClientAddress
Information about the client that sent a request.
interface ClientAddress {
/** The IP address of the client */
address: string
/** The family of the client IP address */
family: 'IPv4' | 'IPv6'
/** The remote port of the client */
port: number
}
Low-level API
For more control over request/response handling, use the low-level API:
import * as http from 'node:http'
import { createRequest, sendResponse } from 'remix/node-fetch-server'
let server = http.createServer(async (req, res) => {
// Convert Node.js request to Fetch API Request
let request = createRequest(req, res, { host: process.env.HOST })
try {
// Add custom headers or middleware logic
let startTime = Date.now()
// Process the request with your handler
let response = await handler(request)
// Make sure the response is mutable
response = new Response(response.body, response)
// Add response timing header
let duration = Date.now() - startTime
response.headers.set('X-Response-Time', `${duration}ms`)
// Send the response
await sendResponse(res, response)
} catch (error) {
console.error('Server error:', error)
res.writeHead(500, { 'Content-Type': 'text/plain' })
res.end('Internal Server Error')
}
})
server.listen(3000)
The low-level API provides:
createRequest(req, res, options) - Converts Node.js IncomingMessage to web Request
sendResponse(res, response) - Sends web Response using Node.js ServerResponse
This is useful for:
- Building custom middleware systems
- Integrating with existing Node.js code
- Implementing custom error handling
- Performance-critical applications
Migration from Express
Transitioning from Express? Here’s a comparison of common patterns:
Basic Routing
let app = express()
app.get('/users/:id', async (req, res) => {
let user = await db.getUser(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json(user)
})
app.listen(3000)
fetch-proxy - Build HTTP proxy servers using the web fetch API