Skip to main content

Built and signed on GitHub Actions

A lightweight and flexible routing library designed for handling complex routes with support for path parameters, wildcard routes, and optional segments

This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
This package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Bun
This package works with Browsers
JSR Score
100%
Published
3 weeks ago (0.3.1)

@cjvnjde/nano-router

JSR Version

@cjvnjde/nano-router is a lightweight and flexible routing library designed for handling complex routes with support for path parameters, wildcard routes, and optional segments. This library is ideal for use in Node.js applications where efficient and dynamic routing is essential. This library was inspired by Fastify, a powerful and feature-rich web framework. However, @cjvnjde/nano-router focuses on providing a minimalistic and focused routing solution without the overhead of additional features that are not always necessary.

Features

  • Simple API: Add routes and match them with minimal configuration.
  • Path Parameters: Supports named parameters in routes for dynamic matching.
  • Wildcard Routes: Match routes with wildcard segments.
  • Optional Parameters: Handle optional segments in routes.
  • Route Grouping: Group routes with common prefixes to keep your code organized.
  • HTTP Method Support: Full support for GET, POST, PUT, DELETE, PATCH, OPTIONS, and HEAD methods

Installation

Install the package using the following command:

npm:

npx jsr add @cjvnjde/nano-router

yarn:

yarn dlx jsr add @cjvnjde/nano-router

pnpm:

pnpm dlx jsr add @cjvnjde/nano-router

bun:

bunx jsr add @cjvnjde/nano-router

deno:

deno add jsr:@cjvnjde/nano-router

Usage

Basic Example

import * as http from 'node:http';
import { Router } from '@cjvnjde/nano-router';

const router = new Router();

router.on('/', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Welcome to the home page!');
});

router.on('/about', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('About us');
});

router.on('/user/:id', (req, res, params) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ userId: params.id }));
});

const server = http.createServer((req, res) => {
  const match = router.match(req.url);

  if (match) {
    match.handler(req, res, match.params);
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

server.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

Handling Wildcard Routes

Wildcard routes allow for flexible matching of paths. For example, you can use them to match static file paths or any other dynamic segment.

router.on('/static/*', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('This is a static file route');
});

router.on('/api/*', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: 'API route' }));
});

Optional Parameters in Routes

Routes can have optional segments. These optional parameters allow you to match different lengths of the URL path.

router.on('/posts/:year/:month?/:day?', (req, res, params) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(params));
});

// Example matches:
// /posts/2024 -> { year: "2024" }
// /posts/2024/08 -> { year: "2024", month: "08" }
// /posts/2024/08/18 -> { year: "2024", month: "08", day: "18" }

Combining Multiple Features

You can combine path parameters, wildcard segments, and optional parameters to create more complex routes.

router.on('/user/:id/profile', (req, res, params) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end(`User Profile for ID: ${params.id}`);
});

router.on('/user/:id/settings', (req, res, params) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end(`Settings for User ID: ${params.id}`);
});

router.on('/files/*', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Serving files');
});

// Example matches:
// /user/123/profile -> User Profile for ID: 123
// /user/123/settings -> Settings for User ID: 123
// /files/images/photo.png -> Serving files

Groupping

You can group routes with a common prefix to simplify route management and keep your code clean. This is especially useful when dealing with APIs or versioned endpoints.

router.group("v1", router => {
  router.on('/static/*', (req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('This is a static file route');
  });

  router.on('/api/*', (req, res) => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'API route' }));
  });
})

Handling HTTP Methods

The MethodRouter allows you to handle different HTTP methods (e.g., GET, POST, PUT, DELETE, etc.) for the same URL pattern. Here's an example:

const methodRouter = new MethodRouter();

methodRouter.get('/items', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: 'GET items' }));
});

methodRouter.post('/items', (req, res) => {
  res.writeHead(201, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: 'Item created' }));
});

// Example matches:
// GET /items -> 'GET items'
// POST /items -> 'Item created'

Middleware-like Grouping for Organizing Complex Routes

You can use the group function to create middleware-like behavior by organizing routes in a hierarchical way.

methodRouter.group('api', (router) => {
  router.get('/users', (req, res) => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'List of users' }));
  });

  router.post('/users', (req, res) => {
    res.writeHead(201, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'User created' }));
  });
});

Error Handling

You can define a default handler to handle unmatched routes, providing a 404 response for routes that are not registered.

const server = http.createServer((req, res) => {
  const match = router.match(req.method, req.url);

  if (match) {
    match.handler(req, res, match.params);
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('404 Not Found');
  }
});

Visualizing the Route Tree

Use the toString() method to print the current structure of the route tree.

console.log(router.toString());

This will output a tree-like structure:

one/:two one/three one/:three/four/:five

└─ root [type: default, params: []]
   └─ one [type: default, params: []]
      ├─ * [type: parametrized, params: [two]]
      │  └─ four [type: default, params: []]
      │     └─ * [type: parametrized, params: [three, five]]
      └─ three [type: default, params: []]

See tests for more examples.

Complexity

The match method theoretically has an O(1) complexity since it uses dictionaries to look up routes (excluding factors like URL splitting).

License

This project is licensed under the MIT License. See the LICENSE file for more details.

Built and signed on
GitHub Actions
View transparency log

Add Package

deno add jsr:@cjvnjde/nano-router

Import symbol

import * as mod from "@cjvnjde/nano-router";

---- OR ----

Import directly with a jsr specifier

import * as mod from "jsr:@cjvnjde/nano-router";

Add Package

npx jsr add @cjvnjde/nano-router

Import symbol

import * as mod from "@cjvnjde/nano-router";

Add Package

yarn dlx jsr add @cjvnjde/nano-router

Import symbol

import * as mod from "@cjvnjde/nano-router";

Add Package

pnpm dlx jsr add @cjvnjde/nano-router

Import symbol

import * as mod from "@cjvnjde/nano-router";

Add Package

bunx jsr add @cjvnjde/nano-router

Import symbol

import * as mod from "@cjvnjde/nano-router";