Skip to content
This repository was archived by the owner on Oct 8, 2025. It is now read-only.

Latest commit

 

History

History

README.md

node-fetch-server

Build portable Node.js servers using web-standard Fetch API primitives

node-fetch-server brings the simplicity and familiarity of the Fetch API to Node.js server development. Instead of dealing with Node's traditional req/res objects, you work with web-standard Request and Response objects—the same APIs you already use in the browser and modern JavaScript runtimes.

Why node-fetch-server?

The Fetch API is already the standard for server development in:

Now you can use the same pattern in Node.js!

Features

  • ✅ Web-standard Request and Response APIs
  • ✅ Drop-in integration with node:http and node:https modules
  • ✅ Streaming response support with ReadableStream
  • ✅ Custom hostname configuration for deployment flexibility
  • ✅ Access to client connection info (IP address, port)
  • ✅ Full TypeScript support with type definitions

Installation

npm install @mjackson/node-fetch-server

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 '@mjackson/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 '@mjackson/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);

Accessing Client Information

Get client connection details (IP address, port) for logging or security:

import { type FetchHandler } from '@mjackson/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 '@mjackson/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');
});

Advanced Usage

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 '@mjackson/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);

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

// Express
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);

// node-fetch-server
import { createRequestListener } from '@mjackson/node-fetch-server';

async function handler(request: Request) {
  let url = new URL(request.url);
  let match = url.pathname.match(/^\/users\/(\w+)$/);

  if (match && request.method === 'GET') {
    let user = await db.getUser(match[1]);
    if (!user) {
      return Response.json({ error: 'User not found' }, { status: 404 });
    }
    return Response.json(user);
  }

  return new Response('Not Found', { status: 404 });
}

http.createServer(createRequestListener(handler)).listen(3000);

Related Packages

  • fetch-proxy - Build HTTP proxy servers using the web fetch API

License

See LICENSE