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