A powerful but lightweight node server built using web standards
A lightweight, modern Node.js HTTP server built entirely around Web Standards. Created to bridge the gap between Node.js servers and native browser APIs, making server-side development feel more familiar to frontend developers.
- Uses native
Request
andResponse
objects for handling HTTP - Built-in support for
AbortSignal
and timeouts - Dynamic imports using standard ESM
import()
- Modern URL handling with
URLPattern
for flexible routing - Standards-first - if it exists in the browser, we use it in Node
- Familiar API for frontend developers
Written in pure ESM and providing a flexible configuration system, this server brings the power and familiarity of Web APIs to your Node.js backend. Whether serving static files, creating a dev server, or building a full API, you'll work with the same standards-based interfaces you already know from frontend development.
Flag | Alias | Default | Description |
---|---|---|---|
--hostname |
-h |
localhost |
The hostname to serve on |
--port |
-p |
8000 |
The port number to listen on |
--path |
-a |
/ |
The path relative to project root to use for the default URL |
--static |
-s |
/ |
Root directory for static files |
--open |
-o |
false |
Open in default browser when server starts |
--timeout |
-t |
undefined |
Server timeout in milliseconds |
--config |
-c |
undefined |
Path to config file |
--debugger |
-d |
false |
Enables logging of errors via console.error
|
npx @shgysk8zer0/http-server
npx @shgysk8zer0/http-server --port=3000 --hostname=0.0.0.0
npx @shgysk8zer0/http-server --static=./public
npx @shgysk8zer0/http-server --config=./http.config.js
Example http.config.js
:
const controller = new AbortController();
export default {
staticRoot: '/static/',
routes: {
'/favicon.svg': '@shgysk8zer0/http-server/api/favicon.js',
'/tasks': '@shgysk8zer0/http-server/api/tasks.js',
},
staticPaths: ['/'],
port: 8000,
signal: controller.signal,
open: true,
};
The server can be imported and configured programmatically:
import { serve } from '@shgysk8zer0/http-server';
const controller = new AbortController();
const config = {
port: 3000,
hostname: 'localhost',
staticRoot: '/public/',
routes: {
'/api/data': './routes/data.js',
'/api/tasks/:taskId': './routes/tasks.js',
'/posts/:year(20\\d{2})/:month(0?\\d|[0-2])/:day(0?[1-9]|[12]\\d|3[01])/:post([a-z0-9\-]+[a-z0-9])': '/js/routes/posts.js',
},
open: true,
signal: controller.signal,
};
const { url, server, whenServerClosed } = await serve(config);
console.log(`Server running at ${url}`);
const resp = await fetch(new URL('/posts/2025/01/30/hello-world', url));
const { title, description, content, author } = await resp.json();
// Handle cleanup when done
controller.abort();
whenServerClosed.then(() => console.log(server, 'closed'));
export default async function(req) {
switch(req.method) {
case 'GET': {
const url = new URL(req.url);
const params = url.searchParams;
return Response.json(Object.fromEntries(params));
}
case 'POST': {
const data = await req.formData();
// Handle request with form data
}
}
}
The server provides a powerful middleware system through requestPreprocessors
and responsePostprocessors
, offering
deep control over the request/response lifecycle.
Request preprocessors run before route handling and can:
- Filter/block requests
- Modify the request object
- Enhance the request context with additional data
- Perform validation or authentication
- Add logging or monitoring
- Abort requests early
- Skip request handling with aborting request or cached response
- Mutate the
context
object with eg geoip data
Each preprocessor receives the request object and a context object containing:
-
ip
- Remote IP address -
url
- Complete request URL -
cookies
- Cookie management -
controller
- Request's abort controller -
signal
- Abort signal for the request
Run on Response
s returned by handlers (or cache) that can:
- Add headers to responses, such as CORS
- Add responses to a cache
- Pipe response streams through transform streams such as
CompressionStream
usingresp.body.pipeThrough()
Both types of middleware can be provided as direct functions, importable modules, or factory functions that return configurable handlers. The system's flexibility allows you to compose complex request/response pipelines while maintaining clean separation of concerns.