node-web-server-with-stuff
v1.4.11
Published
It's just that - a vanilla web server with some of my modules with no thirdparty modules. Yet. It provides an uniform way of server development for serving files and API-endpoints both.
Downloads
124
Maintainers
Readme
node-web-server-with-stuff
A simple and customizable Node.js web server built with the built-in http
module, enhanced with additional utilities for easier development and deployment.
Features
- Vanilla Node.js: Uses Node.js's built-in
http
module, no external web frameworks. - Hot Module Reloading: Automatically reloads modules during development without restarting the server, using
up2require
. - Custom Utilities: Includes helper modules for common tasks:
httpity
: Simplifies HTTP request and response handling.up2require
: Enhancesrequire
for hot reloading.c4console
: Enhancedconsole.log
functionality.
- Easy Deployment: Ready for deployment on platforms like Heroku, Render, and Vercel.
- Environment Aware: Automatically runs in development mode locally and production mode on deployment platforms.
- Customizable: Configurable server options, including port, public directory, API handlers, and access control.
- Modular API Handlers: Supports modular API endpoints with optional access checks.
- Dynamic Static File Serving: Serves static files with intelligent path resolution.
Installation
Install the package via npm:
npm install node-web-server-with-stuff
Quick Start
Create a simple server with default settings:
const { server } = require('node-web-server-with-stuff');
server.run();
This will:
- Start the server on port
3000
locally or use thePORT
environment variable (useful for deployment platforms). - Serve static files from the
/public
directory in the project root. - Serve
index.html
if a request is made to the root URL. - Serve
.html
files if a request is made to a path that doesn't have an extension. - Serve a 404 page if a request is made to a non-existent file.
- Look for API handlers in the
/api
directory. - Run in development mode locally and production mode on deployment platforms.
- Automatically hot reload API handlers during development without restarting the server.
Configuration
You can customize the server by passing an options object to server.run()
:
const { server } = require('node-web-server-with-stuff');
const options = {
port: 5500,
publicPath: '/front',
dev: false, // Force production mode
secure: true, // Enable access control for APIs
accessors: '/checks', // Directory for access control functions
api: ['/data', '/secret_api'], // Directories for API handlers
given: { /* Custom objects to pass to handlers */ },
};
server.run(options);
Available Options
port
(number): Port number to run the server on. Defaults toprocess.env.PORT
or3000
.publicPath
(string): Path to the directory containing static files. Defaults to/public
.dev
(boolean): Set totrue
for development mode orfalse
for production mode. Automatically determined if not set.secure
(boolean): Enable or disable access control for API endpoints. Defaults tofalse
.accessors
(string): Directory containing access control functions. Defaults to/access
.api
(string | string[]): Path(s) to directories containing API handlers. Defaults to/api
.given
(object): Custom objects or functions to pass to API handlers and access control functions.
Static File Serving
The server serves static files from the publicPath
directory and its subdirectories. When a request is made to a URL, the server looks for a corresponding file there.
For example, with publicPath
set to /public
, a request to /about/page
(without extension specified) will look for:
/public/about/page/index.html
.- If no folder named
page
exists in/public/about/
, it will look for a file namedpage
in that directory, without an extension. - If no file named
page
exists in/public/about/
, it will look forpage.html
. - If there's no file named
page.html
either, it will serve the 404 page.
API Handlers
API handlers are modules that process incoming API requests. Place your API handler modules in one or more directories specified by the api
option (default is /api
).
Endpoint Mapping
- A file
/api/notes.js
or/api/notes.cjs
will handle requests to the/api/notes
endpoint. - API handlers can be nested in subdirectories, and the path corresponds to the URL endpoint.
Basic API Handler
An API handler can be a JavaScript file exporting a function:
// /api/notes.js or /api/notes.cjs or similar
module.exports = async function ({ request, response, granted, conn, validateFn, rules, ...rest }) {
// Handle the request and return the response data
const data = await request.data; // Parsed request body and query parameters
// ... perform operations ...
return { success: true, data: /* ... */ };
// Returned data can be any JSON-serializable object
};
Note: Throughout this documentation, req
and resp
are available aliases for request
and response
respectively, so they can be destructured under those names if preferred.
Handler Parameters
request
: Enhanced HTTP request object provided byhttpity
.response
: Enhanced HTTP response object provided byhttpity
.granted
: Result from the access control function (if access control is enabled).conn
,validateFn
,rules
, etc.: Custom objects passed via thegiven
option....rest
: Any additional objects passed via thegiven
option.
Handling Different HTTP Methods
You can define handlers for specific HTTP methods. Instead of exporting a single function, export an object with sub-objects for each HTTP method, specifying the access level and the handler function:
// /api/notes.js or /api/notes.cjs or similar
module.exports = {
GET: {
access: 'guest', // Full access
handler: async function ({ request, response, ...rest }) {
// Handle GET requests
},
},
POST: {
access: 'user', // Requires the /access/user.js function
handler: async function ({ request, response, granted, ...rest }) {
// Handle POST requests with user access
},
},
DELETE: {
access: 'admin', // Requires the /access/admin.js function or the ADMIN_KEY
handler: async function ({ request, response, granted, ...rest }) {
// Handle DELETE requests with admin access
},
},
};
There's also a simpler way to define handlers for specific HTTP methods if you don't need access control:
// /api/notes.js or /api/notes.cjs or similar
module.exports = {
GET: ({ request, response, ...rest }) => {
// Handle GET requests
},
POST: ({ request, response, ...rest }) => {
// Handle POST requests
},
DELETE: ({ request, response, ...rest }) => {
// Handle DELETE requests
},
};
Access Control
When the secure
option is set to true
, API endpoints require access checks before processing requests. Access control functions are placed in the directory specified by the accessors
option (default is /access
).
Default Access Control
Without custom accessors, the server uses a simple cookie-based access control:
- The client must send a cookie named
key
with the value matching theADMIN_KEY
environment variable (customizable via an.env
file). - This provides access levels other than
guest
. - Handlers with
access: 'guest'
are available without any access checks. - If the key is incorrect or missing, the endpoint will respond with
unauthorized
.
Access Levels
- Access levels are arbitrary names and can be customized.
- There can be any number of access levels.
- If
secure: true
and an API handler specifies an access level without a corresponding accessor function in the/access
directory, the endpoint will respond withunauthorized
.
Creating Access Control Functions
Each access level corresponds to a module exporting a function:
// /access/user.js or /access/user.cjs or similar
module.exports = async function ({ request, response, conn, ...rest }) {
const token = request.cookies.token;
const user = await conn.collection('users').findOne({ token });
if (user) {
return user; // Return user data to be available in `granted`
} else {
throw new Error('Unauthorized');
}
};
Using Access Levels in API Handlers
Specify the required access level in the handler definition:
// /api/notes.js
module.exports = {
POST: {
access: 'user', // Requires the `/access/user.js` function
handler: async function ({ request, response, granted, ...rest }) {
// `granted` contains the user data returned from the access control function
},
},
};
If secure: true
and an API handler specifies an access level without a corresponding accessor function, the server will respond with unauthorized
.
Helper Modules
The package provides additional helper modules to simplify development:
up2require
An alias for upgradeToUpdate
from the up2require
module.
During development, modules loaded using the upgraded require
function from up2require
will hot reload. You can add, change, or remove them at any time without restarting the server. This is especially useful for rapid development and testing.
Enabling up2require
in Development Mode
Replace Node.js's require
function with the upgraded one during development:
const { server, up2 } = require('node-web-server-with-stuff');
if (server.dev) {
require = up2(require);
}
To enable hot reloading for a specific module, pass true
as the second argument to the upgraded require
function:
const myModule = require('./myModule', true);
All API handlers are automatically hot-reloaded during development.
c4console
An enhanced version of console.log
from the c4console
module.
const { c } = require('node-web-server-with-stuff');
c('This is an enhanced console log message');
But c
can also be better used like this:
// No imports required to use as a method
[1, 2, 3].map(x => x * 2).c('doubled').map(x => x * 3).c('tripled');
/* Will log: */
// doubled: (3) [2, 4, 6]
// tripled: (3) [6, 12, 18]
Note that the c
method is inherited by any non-empty value from Object.prototype
, takes an optional label argument, and returns the value it was called on. This allows it to be inserted into a chain of property reads or function calls to see intermediate values without breaking the chain logic.
More Examples
Using Custom publicPath
Serve static files from a custom directory:
const { server } = require('node-web-server-with-stuff');
server.run({ publicPath: '/frontend' });
Passing Custom Objects to Handlers
Provide a database connection and other utilities to API handlers:
const { server } = require('node-web-server-with-stuff');
const db = require('./db'); // Your database module
const validateFn = require('./validate');
const rules = require('./rules');
const conn = db.connect(/* credentials */);
server.run({
secure: true,
given: { conn, validateFn, rules },
});
Defining an API Handler with Access Control
// /api/data/notes.js
module.exports = {
GET: {
access: 'user',
handler: async function ({ request, response, granted, conn }) {
const notes = await conn.collection('notes').find({ userId: granted.id }).toArray();
return { notes };
},
},
};
Access Control Function
// /access/user.js
module.exports = async function ({ request, response, conn }) {
const token = request.cookies.token;
const user = await conn.collection('users').findOne({ token });
if (user) {
return user;
} else {
throw new Error('Unauthorized');
}
};