A modern CGI-style web framework built on Hono. Brings Apache-style conventions (.htaccess, .htpasswd) into the modern TypeScript/JSX ecosystem with file-based routing and familiar CGI variables.
- File-based routing -
.cgi.tsx/.cgi.jsxfiles map directly to URL endpoints - CGI-style context - Familiar
$_GET,$_POST,$_SESSION,$_SERVER,$_COOKIE, etc. - Apache compatibility -
.htaccessfor rewrites/redirects/headers,.htpasswdfor Basic Auth - Modern tooling - Full TypeScript support, Vite integration, JSX rendering
- Flexible configuration - Session management, custom middleware, security headers
- Production ready - Comprehensive test coverage, security best practices
npm add @tknf/matchbox hono
# or
pnpm add @tknf/matchbox hono
# or
yarn add @tknf/matchbox honoCreate vite.config.ts:
import devServer from "@hono/vite-dev-server";
import { defineConfig } from "vite";
import { MatchboxPlugin } from "@tknf/matchbox/plugin";
export default defineConfig({
plugins: [
MatchboxPlugin(),
devServer({
entry: "server.ts",
exclude: [/^\/public\/.+/, /^\/favicon\.ico$/],
}),
],
});Create server.ts:
import { createCgi } from "@tknf/matchbox";
export default createCgi();Create public/index.cgi.tsx:
import type { CgiContext } from "@tknf/matchbox";
export default function ({ $_SERVER, $_GET }: CgiContext) {
return (
<html>
<head>
<title>Matchbox</title>
</head>
<body>
<h1>Hello from Matchbox!</h1>
<p>Request Method: {$_SERVER.REQUEST_METHOD}</p>
<p>Query Params: {JSON.stringify($_GET)}</p>
</body>
</html>
);
}npm run devVisit http://localhost:5173 to see your page.
Every page function receives a CgiContext object with:
$_GET- Query parameters$_POST- Form data (POST/PUT)$_FILES- Uploaded files$_REQUEST- Combined$_GET+$_POST+$_COOKIE$_COOKIE- Cookie values$_SERVER- Server and request information$_ENV- Environment variables$_SESSION- Session data
header(name, value)- Set response headerstatus(code)- Set HTTP status coderedirect(url, status?)- Redirect to another URLcgiinfo()- HTML debug info blockget_modules()- List all available CGI modulesget_version()- Get Matchbox version stringlog(message)- Custom loggingrequest_headers()- Get all request headersresponse_headers()- Get current response headers
export default function (ctx: CgiContext) {
const { $_GET, $_POST, $_SESSION, header, status, redirect } = ctx;
// Handle form submission
if ($_POST.username) {
$_SESSION.user = $_POST.username;
return redirect("/dashboard");
}
// Set custom headers
header("X-Custom-Header", "value");
status(200);
return <div>Welcome</div>;
}MatchboxPlugin({
publicDir: "public", // Directory for .cgi files (default: "public")
config: { // Custom config object injected into pages
siteName: "My Site",
apiUrl: "https://api.example.com"
}
});Access config in your pages via context.config.
createCgi({
// Session cookie configuration
sessionCookie: {
name: "_SESSION_ID", // Cookie name (default: "_SESSION_ID")
path: "/", // Cookie path (default: "/")
domain: "example.com", // Cookie domain
secure: true, // HTTPS only (default: false)
sameSite: "Strict", // CSRF protection: "Strict" | "Lax" | "None"
maxAge: 3600, // Session timeout in seconds
},
// URL trailing slash enforcement
enforceTrailingSlash: true,
// Custom middleware (runs before page handlers)
middleware: [
async (c, next) => {
c.header("X-App", "matchbox");
await next();
},
],
// Custom logger
logger: (message, level) => {
console.log(`[${level ?? "info"}] ${message}`);
},
});Place a .htpasswd file in any directory under public/ to protect it:
# Generate with: htpasswd -c .htpasswd username
admin:$apr1$abc123$...
user:$apr1$xyz789$...
All files in that directory and subdirectories will require authentication.
Create .htaccess files to configure rewrites, redirects, headers, and error pages:
# Permanent redirect
Redirect 301 /old-page /new-page
# Conditional rewrite
RewriteCond %{HTTP_HOST} ^www\.example\.com$
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
# Pattern-based rewrite
RewriteRule ^blog/(.+)$ /posts.cgi?slug=$1 [QSA,L]
# Forbidden
RewriteRule ^private$ - [F]
# Security headers
Header set X-Frame-Options "SAMEORIGIN"
Header set X-Content-Type-Options "nosniff"
Header set Strict-Transport-Security "max-age=31536000"
# Custom error pages
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html[L]- Last rule, stop processing[R]/[R=301]/[R=302]- Redirect with status code[F]- Forbidden (403)[G]- Gone (410)[NC]- No Case (case-insensitive)[QSA]- Query String Append[QSD]- Query String Discard[NE]- No Escape
%{HTTP_HOST}- Request host%{HTTP_USER_AGENT}- User agent%{HTTP_REFERER}- Referer header%{REQUEST_URI}- Request URI%{REQUEST_METHOD}- HTTP method%{QUERY_STRING}- Query string%{REMOTE_ADDR}- Client IP- And more... (see documentation)
Check out the examples/ directory for complete working examples:
- basic - Minimal setup with a simple page
- htaccess-auth - Authentication and URL rewriting
- custom-middleware - Custom middleware and logging
- Apache .htaccess Features - Complete
.htaccessfeature reference - Security Guide - Security best practices
- API Reference - Complete API documentation
- Roadmap - Planned features and improvements
Version 0.3.0 introduced enhanced .htaccess parsing. If you're upgrading:
- ✅ Existing
.htaccessfiles work without changes - ✅ Both
[R=301]andR=301flag syntaxes are supported ⚠️ Malformed directives now throw errors (previously silently ignored)
See the migration guide for details.
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build
pnpm run build
# Type check
pnpm run typecheckMIT License - see LICENSE for details.
Contributions are welcome! Please read the contributing guidelines before submitting PRs.