Servie HTTP Interfaces
Servie provides a standard, framework-agnostic set of HTTP interfaces, including `Request`, `Response`, `Body`, `Headers`, and `AbortController`, designed for building interchangeable HTTP clients and servers in both Node.js and browser environments. It aims to offer primitives similar to the Web Fetch API, facilitating universal JavaScript HTTP operations. The current stable version is `4.3.3`, with minor releases frequently addressing bug fixes and introducing small features. Its key differentiators include its dual-environment compatibility without configuration, its focus on raw HTTP interface primitives rather than a full framework, and its modular design which allows it to be used as a foundational layer for various transport mechanisms and middleware libraries.
Common errors
-
TypeError: response.json is not a function
cause The `Body` (and thus `Request` or `Response`) can only be consumed once. If you call `text()`, `json()`, or `arrayBuffer()` on a body, subsequent calls will fail.fixIf you need to read the body multiple times, call `.clone()` first: `const clonedResponse = response.clone(); const data1 = await clonedResponse.json(); const data2 = await response.text();` -
ReferenceError: require is not defined
cause You are attempting to use CommonJS `require()` syntax in an ECMAScript Module (ESM) environment (e.g., `type: "module"` in `package.json` or a `.mjs` file).fixChange your import statements to use ESM syntax: `import { Request, Response } from 'servie';` -
Property 'fetch' does not exist on type 'Global' (or similar TypeScript DOM type conflict)
cause You are likely importing from `servie`'s main entry point in a Node.js TypeScript project, which includes DOM types globally, conflicting with a pure Node.js environment setup.fixExplicitly import from the Node.js distribution: `import { Request, Response } from 'servie/dist/node';` Alternatively, configure your `tsconfig.json` to include appropriate `lib` entries or set `skipLibCheck: true`.
Warnings
- breaking Servie v4.0.10 removed signal forwarding code paths. If your application relied on Servie's internal mechanisms for forwarding `AbortSignal`s, this behavior will have changed or been removed.
- gotcha When using Servie in a Node.js-only TypeScript project, importing from the main `servie` entry point can introduce `dom` types, leading to conflicts or requiring `skipLibCheck`. This is because the main entry point is universal.
- gotcha The types supported for `Body` streams differ between Node.js and browser environments. Node.js supports `Readable` streams, while browsers support `ReadableStream`.
Install
-
npm install servie -
yarn add servie -
pnpm add servie
Imports
- Request
const Request = require('servie').Requestimport { Request } from 'servie' - Response
import Response from 'servie'
import { Response } from 'servie' - Body
const Body = require('servie')import { Body } from 'servie'
Quickstart
import { Request, Response, Body, Headers, AbortController } from "servie";
// A simple Servie-compatible request handler
async function handleRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
console.log(`Received request for: ${url.pathname} with method ${request.method}`);
if (url.pathname === "/greet" && request.method === "POST") {
const name = await request.text();
const responseBody = `Hello, ${name}!`;
const headers = new Headers({ "Content-Type": "text/plain" });
return new Response(responseBody, { status: 200, headers });
}
if (url.pathname === "/json" && request.method === "GET") {
const data = { message: "This is a JSON response", timestamp: new Date().toISOString() };
const headers = new Headers({ "Content-Type": "application/json" });
return new Response(JSON.stringify(data), { status: 200, headers });
}
if (url.pathname === "/abort-test") {
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 100); // Abort after 100ms
try {
await new Promise((resolve, reject) => {
signal.addEventListener('abort', () => reject(new Error('Request aborted')));
// Simulate a long operation that could be aborted
setTimeout(resolve, 500);
});
return new Response("Operation completed before abort.", { status: 200 });
} catch (e: any) {
if (e.message === 'Request aborted') {
return new Response("Operation aborted successfully.", { status: 400 });
}
throw e;
}
}
return new Response("Not Found", { status: 404 });
}
// Example usage:
async function runExamples() {
// 1. Simple GET request
const getRequest = new Request("http://localhost:3000/json", { method: "GET" });
const getResponse = await handleRequest(getRequest);
console.log(`GET /json Status: ${getResponse.status}, Body: ${await getResponse.json().then(data => JSON.stringify(data))}`);
// 2. POST request with a text body
const postRequest = new Request("http://localhost:3000/greet", {
method: "POST",
body: "World",
headers: { "Content-Type": "text/plain" }
});
const postResponse = await handleRequest(postRequest);
console.log(`POST /greet Status: ${postResponse.status}, Body: ${await postResponse.text()}`);
// 3. Aborted request demonstration
const abortRequest = new Request("http://localhost:3000/abort-test", { method: "GET" });
const abortResponse = await handleRequest(abortRequest);
console.log(`GET /abort-test Status: ${abortResponse.status}, Body: ${await abortResponse.text()}`);
}
runExamples().catch(console.error);