npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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

119

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: Enhances require for hot reloading.
    • c4console: Enhanced console.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 the PORT 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 to process.env.PORT or 3000.
  • publicPath (string): Path to the directory containing static files. Defaults to /public.
  • dev (boolean): Set to true for development mode or false for production mode. Automatically determined if not set.
  • secure (boolean): Enable or disable access control for API endpoints. Defaults to false.
  • 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:

  1. /public/about/page/index.html.
  2. If no folder named page exists in /public/about/, it will look for a file named page in that directory, without an extension.
  3. If no file named page exists in /public/about/, it will look for page.html.
  4. 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 by httpity.
  • response: Enhanced HTTP response object provided by httpity.
  • granted: Result from the access control function (if access control is enabled).
  • conn, validateFn, rules, etc.: Custom objects passed via the given option.
  • ...rest: Any additional objects passed via the given 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 the ADMIN_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 with unauthorized.

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');
  }
};

License

MIT