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 🙏

© 2026 – Pkg Stats / Ryan Hefner

kempo-server

v3.1.1

Published

A lightweight, zero-dependency, file based routing server.

Readme

Kempo Server

A lightweight, zero-dependency, file based routing server.

Getting Started

  1. Install the npm package.
npm install kempo-server
  1. Add it to your package.json scripts, use the --root flag to tell it where the root of your site is.
{
  ...
  "scripts": {
    "start": "kempo-server --root public"
  }
  ...
}
  1. Run it in your terminal.
npm run start

Routes

A route is a request to a directory that will be handled by a file. To define routes, create the directory structure to the route and create a file with the name of the method that this file will handle. For example GET.js will handle the GET requests, POST.js will handle the POST requests and so on. Use index.js to handle all request types.

The Javascript file must have a default export that is the function that will handle the request. It will be passed a request, response, and params arguments (See Dynamic Routes below).

For example this directory structure:

my/
├─ route/
│  ├─ GET.js
│  ├─ POST.js
├─ other/
│  ├─ route/
│  │  ├─ GET.js

Would be used to handle GET my/route/, POST my/route/ and GET my/other/route/

HTML Routes

Just like JS files, HTML files can be used to define a route. Use GET.html, POST.html, etc... to define files that will be served when that route is requested.

my/
├─ route/
│  ├─ GET.js
│  ├─ POST.js
├─ other/
│  ├─ route/
│  │  ├─ GET.js
│  ├─ POST.html
│  ├─ GET.html

index fallbacks

index.js or index.html will be used as a fallback for all routes if a method file is not defined. In the above examples we do not have any routes defined for DELETE, PUT PATCH, etc... so lets use an index.js and index.html to be a "catch-all" for all the methods we have not created handlers for.

my/
├─ route/
│  ├─ GET.js
│  ├─ POST.js
│  ├─ index.js
├─ other/
│  ├─ route/
│  │  ├─ GET.js
│  │  ├─ index.js
│  ├─ POST.html
│  ├─ GET.html
│  ├─ index.html
├─ index.html

Dynamic Routes

A dynamic route is a route with a "param" in its path. To define the dynamic parts of the route just wrap the directory name in square brackets. For example if you wanted to get a users profile, or perform CRUD operations on a user you might create the following directory structure.

api/
├─ user/
│  ├─ [id]/
│  │  ├─ [info]/
│  │  │  ├─ GET.js
│  │  │  ├─ DELETE.js
│  │  │  ├─ PUT.js
│  │  │  ├─ POST.js
│  │  ├─ GET.js

When a request is made to /api/user/123/info, the route file api/user/[id]/[info]/GET.js would be executed and receive a request object with request.params containing { id: "123", info: "info" }.

Request Object

Kempo Server provides a request object that makes working with HTTP requests easier:

Properties

  • request.params - Route parameters from dynamic routes (e.g., { id: "123", info: "info" })

  • request.query - Query string parameters as an object (e.g., { page: "1", limit: "10" })

  • request.path - The pathname of the request URL

  • request.method - HTTP method (GET, POST, etc.)

  • request.headers - Request headers object

  • request.url - Full request URL

  • request.body - Pre-parsed request body (JSON → object, form-urlencoded → object, other → raw string, no body → null)

Methods

  • await request.json() - Get cached body parsed as JSON
  • await request.text() - Get cached body as text
  • await request.buffer() - Get cached body as Buffer
  • request.get(headerName) - Get specific header value
  • request.is(type) - Check if content-type contains specified type

Example Route File

Here's an example of what a route file might look like:

// api/user/[id]/GET.js
export default async function(request, response) {
  const { id } = request.params;
  const { includeDetails } = request.query;
  
  // Fetch user data from database
  const userData = await getUserById(id);
  
  if (!userData) {
    return response.status(404).json({ error: 'User not found' });
  }
  
  response.json(userData);
}

POST Request Example

// api/user/[id]/POST.js
export default async function(request, response) {
  const { id } = request.params;
  const { name, email } = request.body;
  
  // request.body is already parsed from JSON
  const updatedUser = await updateUser(id, { name, email });
  
  response.json(updatedUser);
}

Response Object

Kempo Server also provides a response object that makes sending responses easier:

Methods

  • response.json(object) - Send JSON response with automatic content-type
  • response.send(data) - Send response (auto-detects content type)
  • response.html(htmlString) - Send HTML response
  • response.text(textString) - Send plain text response
  • response.status(code) - Set status code (chainable)
  • response.set(field, value) - Set header (chainable)
  • response.type(contentType) - Set content type (chainable)
  • response.redirect(url, statusCode) - Redirect to URL
  • response.cookie(name, value, options) - Set cookie
  • response.clearCookie(name, options) - Clear cookie

Response Methods Examples

The response object supports multiple ways to send responses:

// Different response types
export default async function(request, response) {
  // JSON response
  response.json({ message: 'Hello World' });
  
  // HTML response
  response.html('<h1>Hello World</h1>');
  
  // Text response
  response.text('Hello World');
  
  // Status code chaining
  response.status(201).json({ created: true });
  
  // Custom headers
  response.set('X-Custom-Header', 'value').json({ data: 'test' });
  
  // Redirect
  response.redirect('/login');
  
  // Set cookies
  response.cookie('session', 'abc123', { httpOnly: true }).json({ success: true });
}

Configuration

Kempo Server can be customized with a .config.js file (or .config.json as fallback) to control caching, middleware, security, routing, templating, and more.

For detailed configuration options and examples, see CONFIG.md.

Important: The config file must be placed inside the server root directory (the --root folder). All paths in the config (like customRoutes) are resolved relative to the config file location.

Quick start:

# Create a config file INSIDE the server root
echo 'export default { cache: { enabled: true } };' > public/.config.js

# Use different configs for different environments
kempo-server --root public --config dev.config.js

Features

  • Zero Dependencies - No external dependencies required
  • File-based Routing - Routes are defined by your directory structure
  • Dynamic Routes - Support for parameterized routes with square bracket syntax
  • Wildcard Routes - Map entire directory structures with wildcard patterns
  • Middleware System - Built-in and custom middleware support for authentication, logging, CORS, and more
  • Request Object - Request handling with built-in body parsing
  • Response Object - Response handling with chainable methods
  • Multiple HTTP Methods - Support for GET, POST, PUT, DELETE, and more
  • Static File Serving - Automatically serves static files with proper MIME types
  • HTML Routes - Support for both JavaScript and HTML route handlers
  • Query Parameters - Easy access to URL query parameters
  • Configurable - Customize behavior with a .config.js file
  • Security - Built-in protection against serving sensitive files plus security headers middleware
  • Performance - Smart file system caching, rescan optimization, and optional compression
  • Programmatic Rescan - Trigger a file rescan from anywhere in the Node process without restarting
  • Templating - XML-based templating with templates, pages, fragments, variables, conditionals, and loops

Examples

Simple API Route

// api/hello/GET.js
export default async function(request, response) {
  const { name } = request.query; // Access query parameters like ?name=John
  
  const message = name ? `Hello ${name}!` : 'Hello World!';
  
  response.json({ message });
}

Dynamic User Profile Route

// api/users/[id]/GET.js
export default async function(request, response) {
  const { id } = request.params; // Access route parameters
  const { includeProfile } = request.query; // Access query parameters
  
  // Simulate database lookup
  const user = {
    id: id,
    name: `User ${id}`,
    email: `user${id}@example.com`
  };
  
  if (includeProfile === 'true') {
    user.profile = {
      bio: `Bio for user ${id}`,
      joinDate: '2024-01-01'
    };
  }
  
  response.json(user);
}

JSON API Route

// api/users/[id]/POST.js
export default async function(request, response) {
  const { id } = request.params;
  
  // request.body is pre-parsed based on Content-Type
  const updatedUser = {
    id: id,
    ...request.body,
    updatedAt: new Date().toISOString()
  };
  
  response.json(updatedUser);
}

Form Handling Route

// contact/POST.js
// With Content-Type: application/x-www-form-urlencoded,
// request.body is automatically parsed into an object
export default async function(request, response) {
  const { name, email } = request.body;
  
  // Process form data...
  
  response.html('<h1>Thank you for your message!</h1>');
}

Programmatic HTML Rendering

Use renderPageToString to run the full templating pipeline outside of an HTTP request — ideal for rendering emails, generating HTML to pass to a PDF library, or any other programmatic use case.

import { renderPageToString } from 'kempo-server/templating';

const html = await renderPageToString(pagePath, vars, rootDir);

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | pagePath | string | Absolute path to a .page.html file | | vars | object | Data available as {{varName}} in templates, fragments, and <if> conditions | | rootDir | string | (optional) Root for template/fragment/global search. Defaults to the directory of pagePath |

The same pipeline that runs for web requests is used: template resolution, fragment injection, global content push, <if> conditionals, <foreach> loops, and {{var}} interpolation.

Email example:

emails/
├─ email.template.html          ← shared header/footer for all emails
├─ signature.fragment.html      ← reusable fragment
├─ promo-banner.global.html     ← pushed into every email automatically
├─ welcome.page.html
├─ password-reset.page.html
└─ order-confirmation.page.html
// email.template.html
<html>
  <body>
    <location name="body" />
    <fragment name="signature" />
    <location name="promo" />
  </body>
</html>
// welcome.page.html
<page template="email">
  <content location="body">
    <h1>Welcome, {{userName}}!</h1>
  </content>
</page>
import { renderPageToString } from 'kempo-server/templating';
import path from 'path';

const emailsDir = path.resolve('./emails');
const html = await renderPageToString(
  path.join(emailsDir, 'welcome.page.html'),
  { userName: 'Alice' },
  emailsDir
);
// html is the fully rendered email string — ready to send

Note: vars are merged as state in the pipeline, meaning <page> tag attributes take highest priority, followed by vars, then globals. Built-in vars ({{year}}, {{date}}, {{datetime}}, {{timestamp}}) are always available.

Rendering Pages from External Packages

Use renderExternalPage when a page file lives outside rootDir — for example, in a plugin or extension package — but should be rendered using the host project's templates, fragments, and globals.

import { renderExternalPage } from 'kempo-server/templating';

const html = await renderExternalPage(pageFilePath, rootDir, resolveDir, globals, state, maxDepth);

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | pageFilePath | string | Absolute path to the .page.html file. May live outside rootDir. Used only to read content. | | rootDir | string | Ceiling for template/fragment walk-up. *.global.html files are scanned from here. | | resolveDir | string | Directory within rootDir where template and fragment walk-up starts. pathToRoot is calculated from here. | | globals | object | Global variables available to all pages | | state | object | Per-render variables | | maxDepth | number | Max fragment nesting depth (default 10) |

The behavior is identical to renderPage called on a hypothetical page file physically located at resolveDir/<filename>.page.html. The only difference is that the page content is read from pageFilePath regardless of where it lives on disk.

Example — extension package rendering into host templates:

import { renderExternalPage } from 'kempo-server/templating';
import path from 'path';

// Page lives in the extension package, but uses the host app's templates
const html = await renderExternalPage(
  path.resolve('./node_modules/my-extension/admin/dashboard.page.html'),
  path.resolve('./public'),          // rootDir — host project root
  path.resolve('./public/admin'),    // resolveDir — treat it as if it lives here
  globals,
  state
);

Programmatic File Rescan

When files are added or removed at runtime (e.g., by a CMS generating static pages), you can trigger a file rescan without restarting the server:

import rescan from 'kempo-server/rescan';

// Returns a promise that resolves with the new file count
const fileCount = await rescan();

This works from anywhere in the same Node process — route handlers, middleware, background tasks, file watchers, or any other code running alongside the server.

// Example: CMS generates a page and makes it immediately available
import { writeFile } from 'fs/promises';
import rescan from 'kempo-server/rescan';

const html = buildPage(theme, template, content);
await writeFile('./public/new-page.html', html);
await rescan();
// New page is now live

Command Line Options

Kempo Server supports several command line options to customize its behavior:

  • --root <path> - Set the document root directory (required)
  • --port <number> - Set the port number (default: 3000)
  • --host <address> - Set the host address (default: localhost)
  • --config <path> - Set the configuration file path (default: .config.json)
  • --verbose - Enable verbose logging
kempo-server --root public --port 8080 --host 0.0.0.0 --verbose

Note: To enable automatic rescanning for new files during development, set maxRescanAttempts in your config file (default is 3). Set to 0 to disable rescanning.

Testing

This project uses the Kempo Testing Framework. Tests live in the tests/ folder and follow these naming conventions:

  • [name].node-test.js — Node-only tests
  • [name].browser-test.js — Browser-only tests
  • [name].test.js — Runs in both environments

Run tests

Using npm scripts:

npm run tests         # Run all tests (Node + Browser)
npm run tests:node    # Run Node tests only
npm run tests:browser # Run Browser tests only
npm run tests:gui     # Start the GUI test runner

For advanced usage (filters, flags, GUI options), see: https://github.com/dustinpoissant/kempo-testing-framework

Single-Page Applications

Kempo Server makes it easy to serve SPAs by using customRoutes to redirect all HTML requests to your shell page while still serving individual page fragments from a pages/ directory.

See SPA.md for a full walkthrough.

Documentation