Customization
Business logic
Define HTTP endpoints in your rules file to implement custom business logic next to GraphQL.
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.
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.
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.
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
gqltemplate tag from@carv/rulesfor GraphQL strings. c.env.graphql(source, variables?, operationName?)returns the fullExecutionResultwhen 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/hasSomec.env.viewer.hasRole({ ... })andhasGroup({ ... })c.env.tokenfor raw token information
Example: restrict an endpoint to administrators by checking group membership.
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.logfor your own logs:c.env.log.info(...),c.env.log.error(...).
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.
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.
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 instanceenv: 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:
[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.