@fs-router/hono
v0.0.9
Published
File-based routing for Hono
Downloads
73
Readme
@fs-router/hono
File-based routing for Hono - brings Next.js-style routing conventions to Hono with zero configuration. Works on any JavaScript runtime.
Features
- 🚀 Zero Configuration
- 📂 File-System Based Routes
- 🛠️ Full TypeScript Support
- ⚡ Ultra Fast Performance
- 🌐 Universal - Node.js, Bun, Deno, Cloudflare Workers, etc.
- 🔌 Automatic Middleware Detection
- 🎯 Hono Native Routing
Installation
npm install @fs-router/hono
# or
yarn add @fs-router/hono
# or
pnpm add @fs-router/honoQuick Start
import { Hono } from 'hono';
import { useFsRouter } from '@fs-router/hono';
const app = new Hono();
await useFsRouter(app, {
routesDir: 'routes', // Use 'src/routes' if using src folder
verbose: true
});
export default app;Runtime-Specific Setup
Node.js:
export default app;Bun:
export default {
port: 3000,
fetch: app.fetch,
};Deno:
Deno.serve(app.fetch);Cloudflare Workers:
export default app;Route Conventions
File Structure to Routes
| File Path | Hono Route | Description |
|-----------|------------|-------------|
| route.ts | / | Root route |
| users.route.ts | /users | Simple route |
| users/[id].route.ts | /users/:id | Dynamic parameter |
| posts/[...slug].route.ts | /posts/* | Catch-all route |
| api/v1/users.route.ts | /api/v1/users | Nested route |
HTTP Method Handlers
Export functions named after HTTP methods:
// routes/users/[id].route.ts
import type { Context } from 'hono';
export const GET = async (c: Context) => {
const id = c.req.param('id');
const user = await db.users.findById(id);
if (!user) {
return c.json({ error: 'Not found' }, 404);
}
return c.json({ user });
};
export const POST = async (c: Context) => {
const body = await c.req.json();
const user = await db.users.create(body);
return c.json({ user }, 201);
};
export const DELETE = async (c: Context) => {
const id = c.req.param('id');
await db.users.delete(id);
return c.json({ deleted: true }, 204);
};
// Fallback for other methods
export default async (c: Context) => {
return c.json({ error: 'Method not allowed' }, 405);
};Middleware Files
Create middleware with .middleware.ts suffix:
// routes/auth.middleware.ts
import type { Context, Next } from 'hono';
export default async (c: Context, next: Next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return c.json({ error: 'Unauthorized' }, 401);
}
try {
const user = await verifyToken(token);
c.set('user', user);
await next();
} catch (error) {
return c.json({ error: 'Invalid token' }, 401);
}
};Examples
REST API Structure
routes/
├── api/
│ ├── auth/
│ │ ├── login.route.ts # POST /api/auth/login
│ │ ├── register.route.ts # POST /api/auth/register
│ │ └── [...rest].middleware.ts # Middleware for /api/auth/* (all auth routes)
│ ├── users/
│ │ ├── route.ts # GET, POST /api/users
│ │ └── [id].route.ts # GET, PUT, DELETE /api/users/:id
│ └── protected.middleware.ts # Middleware for /api/protected only
└── health.route.ts # GET /healthError Handling
// routes/users/[id].route.ts
import { HTTPException } from 'hono/http-exception';
export const GET = async (c: Context) => {
try {
const user = await db.users.findById(c.req.param('id'));
if (!user) {
throw new HTTPException(404, { message: 'User not found' });
}
return c.json({ user });
} catch (error) {
if (error instanceof HTTPException) throw error;
throw new HTTPException(500, { message: 'Internal error' });
}
};Catch-All Routes
// routes/docs/[...path].route.ts
export const GET = async (c: Context) => {
const path = c.req.param('path') || '';
return c.json({ documentation: `Docs for ${path}` });
};Validation with Zod
// routes/users.route.ts
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
export const POST = async (c: Context) => {
try {
const body = await c.req.json();
const data = userSchema.parse(body);
const user = await db.users.create(data);
return c.json({ user }, 201);
} catch (error) {
if (error instanceof z.ZodError) {
return c.json({ error: 'Validation failed', details: error.errors }, 400);
}
throw error;
}
};Context Variables
// routes/protected/profile.route.ts
type Variables = { user: { id: string; email: string } };
export const GET = async (c: Context<{ Variables: Variables }>) => {
const user = c.get('user'); // Set by auth middleware
const profile = await db.profiles.findById(user.id);
return c.json({ profile });
};API Reference
useFsRouter(app, options)
await useFsRouter(app, {
routesDir: string; // Required: Path to routes directory
verbose?: boolean; // Optional: Enable logging (default: false)
});HonoAdapter
For advanced usage:
import { HonoAdapter } from '@fs-router/hono';
import { createRouter } from '@fs-router/core';
const adapter = new HonoAdapter(app);
await createRouter(adapter, {
routesDir: 'routes',
verbose: true
});Integration with Hono Middleware
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { secureHeaders } from 'hono/secure-headers';
import { useFsRouter } from '@fs-router/hono';
const app = new Hono();
// Global middleware
app.use('*', logger());
app.use('*', cors());
app.use('*', secureHeaders());
// File-based routes
await useFsRouter(app, {
routesDir: 'routes'
});
export default app;Runtime Examples
Cloudflare Workers
// worker.ts
import { Hono } from 'hono';
import { useFsRouter } from '@fs-router/hono';
type Bindings = {
DB: D1Database;
BUCKET: R2Bucket;
};
const app = new Hono<{ Bindings: Bindings }>();
await useFsRouter(app, {
routesDir: 'routes'
});
export default app;
// routes/api/data.route.ts
export const GET = async (c: Context<{ Bindings: Bindings }>) => {
const result = await c.env.DB.prepare("SELECT * FROM users").all();
return c.json(result);
};Deno Deploy
import { Hono } from 'https://deno.land/x/hono/mod.ts';
import { useFsRouter } from 'npm:@fs-router/hono';
const app = new Hono();
await useFsRouter(app, {
routesDir: 'routes'
});
Deno.serve(app.fetch);Migration Guide
Before (Hono Routes)
app.get('/users', (c) => c.json({ users: [] }));
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }));After (FS Router)
// routes/users.route.ts
export const GET = (c) => c.json({ users: [] });
// routes/users/[id].route.ts
export const GET = (c) => c.json({ id: c.req.param('id') });Contributing
We welcome contributions! Please visit our GitHub repository to:
- 🐛 Report bugs
- 💡 Request features
- 🔧 Submit pull requests
- ⭐ Star the project if you find it useful!
License
MIT © Universal FS Router
