Skip to content

Using with H3

Quick Start

CommonJS

javascript
const { H3, serve } = require('h3');
const Router = require('clear-router/h3');

const app = new H3();

Router.get('/hello', () => {
  return 'Hello World';
});

Router.apply(app);

serve(app, { port: 3000 });

ESM

javascript
import { H3, serve } from 'h3';
import Router from 'clear-router/h3';

const app = new H3();

Router.get('/hello', ({ res }) => {
  res.send('Hello World');
});

await Router.apply(app);

serve(app, { port: 3000 });

TypeScript

typescript
import express from 'express';
import Router from 'clear-router/h3';

const app = new H3();

Router.get('/hello', ({ res }) => {
  res.send('Hello World');
});

await Router.apply(app);

serve(app, { port: 3000 });

Usage Examples

Basic Route

javascript
Router.get('/hello', () => {
  return 'Hello World';
});

With Middleware

javascript
const authMiddleware = (evt, next) => {
  // auth logic
  next();
};

Router.post('/secure', () => 'Protected', [authMiddleware]);

Method Override

Clear Router supports HTTP method override for POST requests using form body keys or headers.

Default override keys:

  • Body: _method
  • Header: X-HTTP-Method
javascript
Router.put('/users/:id', (event) => {
  return { method: event.req.method, id: event.context.params.id };
});

// POST /users/12 with body { "_method": "PUT" }

Custom keys are supported:

javascript
Router.configure({
  methodOverride: {
    bodyKeys: ['_method', 'method'],
    headerKeys: ['x-http-method', 'x-method-override'],
  },
});

Disable override behavior:

javascript
Router.configure({ methodOverride: { enabled: false } });

Request Body Access via ctx.req.getBody()

clear-router patches H3 requests with ctx.req.getBody() so body access is consistent in handlers and controllers.

  • Always available in route handlers.
  • Returns parsed JSON/form/multipart body when present.
  • Returns {} when the request has no body.
javascript
Router.post('/users', (ctx) => {
  const body = ctx.req.getBody();
  return { hasName: Boolean(body.name) };
});

Router.get('/status', (ctx) => {
  // Safe even for GET requests without a body
  return { body: ctx.req.getBody() }; // {}
});

Controller Binding

javascript
class UserController {
  index() {
    return 'User List';
  }
}

Router.get('/users', [UserController, 'index']);

Class-based handlers will auto-bind to static or instance methods.

Custom Controllers (Extending the Base Controller)

For advanced use cases, you can create project-specific controller base classes that extend the clear-router Controller.

typescript
// Example app-level base controller
class AppController extends Controller<H3Event> {
  ok(data: any) {
    return { success: true, data };
  }

  get userId() {
    return this.params?.id;
  }
}

class UserController extends AppController {
  show() {
    return this.ok({ id: this.userId, query: this.query });
  }
}

Router.get('/users/:id', [UserController, 'show']);

Benefits

  • Centralizes shared response helpers and controller utilities.
  • Reuses hydrated request data (this.ctx, this.body, this.query, this.params, this.clearRequest) consistently.
  • Reduces repeated boilerplate across controllers.
  • Makes controller behavior easier to standardize and test.

Handler Arguments and ClearRequest

H3 handlers are invoked with:

  1. ctx: H3 event
  2. clearRequest: ClearRequest | undefined
javascript
Router.put('/users/:id', (event, clearRequest) => {
  return {
    method: event.req.method,
    hasClearRequest: Boolean(clearRequest),
  };
});

For controller instance handlers ([ControllerClass, 'method']), router hydration includes:

  • this.body (from ctx.req.getBody())
  • this.query (query params)
  • this.params (route params)
  • this.clearRequest (normalized request wrapper)

API Resource Binding

You can also bind routes to API resources:

javascript
class UserController {
  index({ res }) {
    return [{ name: 'User 1' }, { name: 'User 2' }];
  }
  show({ res }) {
    returnn { name: 'User 1' };
  }
  create({ res }) {
    return 'User created';
  }
  update({ res }) {
    return 'User updated';
  }
  destroy({ res }) {
    return 'User deleted';
  }
}

Router.apiResource('/users', UserController);

Grouped Routes

javascript
Router.group('/admin', () => {
  Router.get('/dashboard', () => 'Admin Panel');
});

Async group callbacks are also supported:

javascript
await Router.group('/api', async () => {
  await loadRoutes();
  Router.get('/status', () => ({ ok: true }));
});

With middleware:

javascript
Router.group(
  '/secure',
  () => {
    Router.get('/data', () => 'Secure Data');
  },
  [authMiddleware],
);

Global Middleware Scope

javascript
Router.middleware([authMiddleware], () => {
  Router.get('/profile', () => 'My Profile');
});

Multiple HTTP Methods

javascript
Router.add(['get', 'post'], '/handle', ({ req }) => {
  return `Method: ${req.method}`;
});

Inspecting Routes

javascript
Router.get('/hello', ({ res }) => 'Hello');
Router.post('/world', ({ res }) => 'World');

const allRoutes = Router.allRoutes();
console.log(allRoutes);
// Output:
// [
//   { methods: ['get'], path: '/hello', middlewareCount: 0, handlerType: 'function' },
//   { methods: ['post'], path: '/world', middlewareCount: 0, handlerType: 'function' }
// ]

API Reference

See API for complete API documentation.

Middleware Execution Order

txt
[ Global Middleware ] → [ Group Middleware ] → [ Route Middleware ]

Handler Execution

  • If function: executed directly
  • If [Controller, 'method']: auto-instantiated (if needed), method is called
  • First argument is always H3 event context
  • Second argument is clearRequest (defined for controller handlers)

Testing

bash
npm test              # Run all tests
npm run test:esm      # Test ESM
npm run test:ts       # Test TypeScript

See Testing for detailed testing guide.

Examples

bash
npm run example       # CommonJS example
npm run example:esm   # ESM example
npm run example:ts    # TypeScript example

Check example/ directory for full working demos.