1. Customization
  2. Business logic

Customization

Business logic

Define HTTP endpoints in your rules file to implement custom business logic next to GraphQL.

TIP

Make sure you have configured a rules file first. See Access Restrictions for how to point IdentityHub to your rules.js.

Define endpoints in your rules file

In addition to access rules, a rules file can export HTTP endpoints. Use defineEndpoints from @carv/rules. Endpoints are mounted under the base path /api.

js
import { defineEndpoints } from '@carv/rules';

export const endpoints = defineEndpoints(({ app }) => {
	app.get('/hello', (c) => c.text('Hello World'));
});
  • Base path: The example above responds on GET /api/hello.
  • Router: Endpoints use Hono. You can use any Hono routing/middleware APIs.

Validate inputs with Valibot

You get v and vValidator in the endpoints context to validate request data using Valibot.

js
import { defineEndpoints } from '@carv/rules';

export const endpoints = defineEndpoints(({ app, v, vValidator }) => {
	app.get(
		'/greet',
		vValidator(
			'query',
			v.object({
				name: v.pipe(v.optional(v.string(), 'World'), v.minLength(1)),
			}),
		),
		(c) => {
			const { name } = c.req.valid('query');
			return c.text(`Hello ${name}!`);
		},
	);
});

Supported targets for vValidator are 'json', 'form', and 'query'.

Call GraphQL from an endpoint

Endpoints can call back into IdentityHub via c.env.query (cached for the current request) or c.env.graphql.

js
import { defineEndpoints, gql } from '@carv/rules';

export const endpoints = defineEndpoints(({ app }) => {
	app.get('/viewer', async (c) => {
		const data = await c.env.query(gql`
			query ApiViewer {
				viewer {
					id
					dn
				}
			}
		`);

		return c.json(data);
	});
});
  • Use the gql template tag from @carv/rules for GraphQL strings.
  • c.env.graphql(source, variables?, operationName?) returns the full ExecutionResult when you need errors as well.

Authorization and identity helpers

Within endpoints the current viewer and token are available on c.env:

  • c.env.viewer.has(access) / hasEvery / hasSome
  • c.env.viewer.hasRole({ ... }) and hasGroup({ ... })
  • c.env.token for raw token information

Example: restrict an endpoint to administrators by checking group membership.

js
export const endpoints = defineEndpoints(({ app }) => {
	app.get('/admin/ping', async (c) => {
		const isAdmin = await c.env.viewer.hasGroup({ dn: 'cn=admin,ou=groups,o=data' });
		if (!isAdmin) {
			return c.text('Forbidden', 403);
		}
		return c.text('pong');
	});
});

Error handling and logging

  • Throw new c.HTTPException(status, { message, cause }) to signal errors. Uncaught errors are logged and turned into HTTP responses.
  • Use the structured logger c.env.log for your own logs: c.env.log.info(...), c.env.log.error(...).
js
export const endpoints = defineEndpoints(({ app, HTTPException }) => {
	app.post('/unsafe', async (c) => {
		throw new HTTPException(400, { message: 'Bad request' });
	});
});

Group routes into modules

You can split larger endpoint trees into modules and mount them via app.route.

js
export const endpoints = defineEndpoints(async ({ app }) => {
	const { default: Reports } = await import('./reports/roles.ts');
	app.route('/reports', Reports);
});

Alternative: return a fetch handler

Instead of using app, you can return an object with a fetch(..) method compatible with Hono’s interface.

js
export const endpoints = defineEndpoints({
	async fetch() {
		return new Response('Hello World');
	},
});

What’s available in the endpoints context?

The factory receives a context with:

  • app: a Hono app instance
  • env: IdentityHub bindings (logger, config, decodeGlobalId, query/graphql, viewer, token)
  • Utilities: v, vValidator, csv, iconv
  • Hono extras: HTTPException, cors, secureHeaders, prettyJSON, timeout, bodyLimit, etag, ipRestriction, timing, cookies and streaming helpers, etc.

Refer to Hono’s docs for middleware usage, and use c.env for IdentityHub-specific capabilities.

Configuration recap

Point IdentityHub to your rules file in local.toml:

local.toml
[rules]
file = "./rules.js"

Once exported, your endpoints are available under /api/.... No server restart is required in development; changes to the rules file are hot-reloaded.