@qubitcodes/qcrouter
v1.3.1
Published
Type-safe routing compiler and middleware chainer for Next.js and modern JavaScript runtimes.
Downloads
574
Maintainers
Readme
@qubitcodes/qcrouter 🚀
@qubitcodes/qcrouter is a lightweight, zero-dependency routing compiler and sequential middleware chainer. Written in strict TypeScript, it compiles dynamic route trees into pre-compiled RegExp matchers for fast request-time matching.
It is framework-agnostic, working in Node.js, Bun, Deno, Astro, SvelteKit, Hono, and Next.js (App Router Edge Middleware).
Key Features
- Compiled Matchers: Compiles route path patterns (e.g.
/users/:id) once at boot-time into cached RegExp matchers to avoid request-time allocations. - Agnostic Core: Middleware runner matches standard Web API
RequestandResponseinterfaces, making it compatible with any modern JS runtime. - Type Safety: Provides compile-time autocompletion of route names and path parameters in your editor.
- Next.js Edge Compatibility: Includes a dedicated proxy chainer sub-module (
@qubitcodes/qcrouter/next) compatible with Edge Middleware runtimes. - Dual Bundle: Ships with ES Modules (
esm/) and CommonJS (cjs/) targets.
Installation
npm install @qubitcodes/qcrouterQuick Start (Automated Setup)
Instead of manually creating proxy files, hooks, or configurations, qcrouter includes an interactive CLI setup wizard that automates the integration for you:
npx qcrouter initWhat this automated command does:
- Auto-Detects Your Web Stack: Automatically scans your
package.jsonto identify if you are running in Next.js, Astro, SvelteKit, or Hono. - File Scan: Scans dynamically for existing hook/middleware files (checking both the
src/directory and root). If not found, it checks if asrc/directory is present to safely initialize the integration file inside it (e.g.src/hooks.server.tsorsrc/proxy.ts), falling back to your project root otherwise. - Safety Backup: If a target file already exists, the CLI will log a warning and automatically save a timestamped backup (e.g.
src/proxy.ts.bak-2026-05-28-175325) so you never lose your code! - Writes Templates: Automatically writes framework-specific template integrations matching Next.js proxies, Astro middlewares, SvelteKit Hooks, Hono registers, or a helpful Custom Node.js Express/Fastify initializer.
- Generates Configs: Generates boilerplate
qcRouter.config.tsandmiddleware.config.tsin your project root!
Architectural Philosophy: Pure Router vs. Utility Overlay
Because @qubitcodes/qcrouter is 100% framework-agnostic and built using standard Web API signatures (Request, Response), it adapts to the design pattern of your specific web stack:
1. Traditional Frameworks (Hono, Express, Fastify, Client-Side SPAs)
- Status: Primary Routing Engine
- Role: In standard declaration-based architectures,
@qubitcodes/qcrouteris your sole and absolute routing engine. When you change a path pattern (e.g. from/terminalsto/terminals1) in your central config tree, it instantly updates the live request matcher, route controllers, and dynamic URL generation (route()) in one single step.
2. File-System Based Frameworks (Next.js App Router, Astro, SvelteKit)
- Status: MVC Utility Overlay & Middleware Proxy
- Role: Because these frameworks handle visual page loading using physical directories on disk (e.g.,
src/app/terminals/page.tsx), the framework's filesystem is the visual routing engine. - How it is consumed: In these environments, you do not use
qcrouterto load or serve visual pages. Instead, you leverage it to enforce clean MVC architecture:- Named Route Resolvers (
route()): Dynamically resolves named route keys within your project components (e.g. headers, footer links) instead of hardcoding raw strings. If a path changes, you simply rename the physical folder to match and update your config; all dynamic UI links update automatically! - Edge Middleware Proxy Gateway (
createNextProxy): Intercepts incoming requests at the Edge (middleware.ts) before they load pages, parsing paths and chaining sequential route-specific middleware pipelines (such as token validation, rate limiting, and request logs).
- Named Route Resolvers (
1. Agnostic Core Setup (Standard JS/TS, Hono, Astro, SvelteKit)
Define your routes configuration and register it with defineRouter:
// qcRouter.config.ts
import { defineRouter } from '@qubitcodes/qcrouter';
// 1. Declare route tree configuration with 'as const'
export const qcRouterConfig = {
web: {
prefix: '',
middleware: ['log'],
routes: {
home: '/',
profile: '/users/:id',
}
},
api: {
prefix: '/api',
middleware: ['auth'],
routes: {
users: {
path: '/users',
method: 'GET'
}
}
}
} as const;
export const qcRouter = defineRouter(qcRouterConfig);
export const { flatRoutes, route } = qcRouter;Now, in any other component or script, import { route } from your local qcRouter.config.ts and get type-safe autocompletion on route names and required path parameters:
// AnyComponent.tsx
import { route } from './qcRouter.config';
// Resolves to "/users/123" with compile-time verification
const url = route('profile', { id: 123 });For non-web groups, route names are prefixed by the group key:
route('api.users');2. Next.js App Router Edge Proxy Gateway (qcrouter/next)
Use the dedicated Next.js Edge adapter to create a secure routing proxy by importing the pre-compiled flatRoutes from your config:
// src/proxy.ts (Next.js Edge Middleware Proxy)
import { NextRequest, NextResponse } from 'next/server';
import { createNextProxy, NextMiddlewareHandler } from '@qubitcodes/qcrouter/next';
import { flatRoutes } from '../qcRouter.config';
import { MIDDLEWARE_MAP } from '../middleware.config';
// Create the Edge Proxy Gateway Runner
export const proxy = createNextProxy({
flatRoutes,
middlewareMap: MIDDLEWARE_MAP,
fallbackHandler: () => NextResponse.next() // Proceed to pages if no route matched
});3. Dynamic URL Parameters (Single & Multiple)
qcrouter supports resolving routes with single or multiple path parameters. Autocomplete typing is preserved automatically during editor resolution:
// Define path containing multiple unique parameters:
const config = {
web: {
routes: {
postComment: '/users/:userId/posts/:postId/comments/:commentId'
}
}
} as const;
export const { route } = defineRouter(config);
// Resolving multiple parameters with complete compile-time validation:
const url = route('postComment', {
userId: 'jkthoppil',
postId: 987,
commentId: 42
});
// Outputs: "/users/jkthoppil/posts/987/comments/42"4. Route Grouping, Nesting, & Middleware Exceptions
You can recursively group and nest routes inside the configuration. Nested routes inherit prefix names, path prefixes, and parent middleware arrays:
const config = {
admin: {
prefix: '/admin',
middleware: ['auth', 'adminRole'], // Inherited by all children
routes: {
dashboard: '/dashboard', // Path: /admin/dashboard
analytics: {
path: '/analytics', // Path: /admin/analytics
middleware: ['perfLog'] // Runs: ['auth', 'adminRole', 'perfLog']
},
login: {
path: '/login', // Path: /admin/login
withoutMiddleware: ['auth', 'adminRole'] // Exempts itself from parent restrictions
}
}
}
} as const;5. Library Engine vs. Application Layer (Architecture boundaries)
When you run npm install @qubitcodes/qcrouter, the installer downloads only the compiled compiler and pipeline executor (dist/).
- The Library (
@qubitcodes/qcrouter): Is non-intrusive. It does not auto-inject file layouts (like asrc/middlewaresfolder orproxy.ts) into your project. - The Application: You create your own local
src/proxy.tsandsrc/middlewares/directory. You import@qubitcodes/qcrouterinside your proxy gateway to orchestrate your custom, environment-specific middleware callback functions.
6. Multi-Stack Integrations (Express, Hono, Astro, SvelteKit)
Because the core sequential chainer is 100% framework-agnostic, you can easily integrate it to run middleware pipelines in standard Node.js servers, edge frameworks, and serverless environments using the pre-compiled flatRoutes and MIDDLEWARE_MAP from your config:
Express Middleware Setup
import express from 'express';
import { runMiddlewareChain } from '@qubitcodes/qcrouter';
import { flatRoutes } from './qcRouter.config';
import { MIDDLEWARE_MAP } from './middleware.config';
const app = express();
app.use((req, res, next) => {
// Match RegExp & HTTP method
const matched = Object.values(flatRoutes).find((r) => {
return r.regex.test(req.path) && (!r.method || r.method === req.method);
});
if (!matched) return next();
const handlers = matched.middlewares.map((name) => MIDDLEWARE_MAP[name]).filter(Boolean);
return runMiddlewareChain(req, handlers, next);
});Hono Pipeline Setup
import { Hono } from 'hono';
import { runMiddlewareChain } from '@qubitcodes/qcrouter';
import { flatRoutes } from './qcRouter.config';
import { MIDDLEWARE_MAP } from './middleware.config';
const app = new Hono();
app.use('*', async (c, next) => {
const matched = Object.values(flatRoutes).find(r => r.regex.test(c.req.path));
if (!matched) return next();
const handlers = matched.middlewares.map(name => MIDDLEWARE_MAP[name]).filter(Boolean);
return runMiddlewareChain(c, handlers, () => next());
});Astro Middleware Gateway (src/middleware.ts)
import { runMiddlewareChain } from '@qubitcodes/qcrouter';
import { defineMiddleware } from 'astro/middleware';
import { flatRoutes } from '../qcRouter.config';
import { MIDDLEWARE_MAP } from '../middleware.config';
export const onRequest = defineMiddleware((context, next) => {
const matched = Object.values(flatRoutes).find(r => r.regex.test(context.url.pathname));
if (!matched) return next();
const handlers = matched.middlewares.map(name => MIDDLEWARE_MAP[name]).filter(Boolean);
return runMiddlewareChain(context, handlers, () => next());
});SvelteKit Hook Gateway (src/hooks.server.ts)
import { runMiddlewareChain } from '@qubitcodes/qcrouter';
import type { Handle } from '@sveltejs/kit';
import { flatRoutes } from '../qcRouter.config';
import { MIDDLEWARE_MAP } from '../middleware.config';
export const handle: Handle = async ({ event, resolve }) => {
const matched = Object.values(flatRoutes).find(r => r.regex.test(event.url.pathname));
if (!matched) return resolve(event);
const handlers = matched.middlewares.map(name => MIDDLEWARE_MAP[name]).filter(Boolean);
return runMiddlewareChain(event, handlers, () => resolve(event));
};7. API & Configuration Reference
A. defineRouter(config)
defineRouter is the recommended public API for registering a route config:
import { defineRouter } from '@qubitcodes/qcrouter';
export const qcRouter = defineRouter(qcRouterConfig);
export const { flatRoutes, route } = qcRouter;It replaces the older manual setup:
const flatRoutes = compileRoutes(qcRouterConfig);
const { route } = createRouter(flatRoutes);compileRoutes and createRouter remain available for advanced or low-level integrations.
B. Configuration Schema
RouteConfig Properties
The core configuration schema for individual route nodes.
| Property | Type | Description |
| :--- | :--- | :--- |
| path | string (Required) | The URL path pattern. Supports dynamic variables prefixed with : (e.g. /users/:id). |
| method | string (Optional) | HTTP request method constraint (e.g. 'GET', 'POST', 'DELETE'). |
| middleware | string[] (Optional) | Route-level middlewares to append to the pipeline. |
| withoutMiddleware | string[] (Optional) | Group-level middlewares to bypass or exempt from this specific route. |
RouteGroup Properties
The configuration schema for recursive nesting groups.
| Property | Type | Description |
| :--- | :--- | :--- |
| prefix | string (Optional) | Base path prefix appended automatically to all child routes (e.g. /api/v1). |
| namePrefix | string (Optional) | Name prefix prepended dynamically to child route names (e.g. 'api.'). |
| middleware | string[] (Optional) | Group-level middlewares inherited by all child routes. |
| routes | Record<string, string \| RouteConfig \| RouteGroup> (Required) | Child definitions tree containing nested routes or groups. |
C. Type Safety & Parameter Autocomplete
@qubitcodes/qcrouter uses conditional TS types to parse path variables. If a route pattern is:
posts: '/users/:userId/posts/:postId'Your editor will require both parameters at compile-time:
// Enforces passing required userId and postId:
route('posts', { userId: 'jkthoppil', postId: 123 });Missing parameters or typing an incorrect route name will trigger a TS compiler error immediately, preventing runtime URL bugs!
