Backend JSRuntime GuidesNode.js
Hapi
Integrate @wacht/backend in Hapi with route pre-handlers and permission guards.
This Hapi guide covers a full admin-service flow using pre-handlers, permission checks, and typed backend API calls.
Project structure
Hapi Admin Service
src/
server.ts
auth/
require-permission.ts
routes/
organizations.ts
1. Bootstrap server + client
import Hapi from '@hapi/hapi';
import { initClient } from '@wacht/backend';
import { registerOrganizationRoutes } from './routes/organizations';
initClient({
apiKey: process.env.WACHT_API_KEY!,
baseUrl: process.env.WACHT_BACKEND_API_URL,
});
const server = Hapi.server({ port: 3000, host: '0.0.0.0' });
registerOrganizationRoutes(server);
export { server };2. Auth pre-handler
import type Hapi from '@hapi/hapi';
import { getAuthFromToken } from '@wacht/backend';
async function requirePermission(request: Hapi.Request, h: Hapi.ResponseToolkit, permission: string) {
const authz = request.headers.authorization;
const token =
authz && authz.startsWith('Bearer ') ? authz.slice(7).trim() : null;
const auth = await getAuthFromToken(token);
try {
await auth.protect({ permission });
} catch {
return h.response({ error: 'forbidden' }).code(403).takeover();
}
(request.app as any).wachtAuth = auth;
return h.continue;
}
export { requirePermission };3. Protected route
import type Hapi from '@hapi/hapi';
import { organizations } from '@wacht/backend';
import { requirePermission } from '../auth/require-permission';
export function registerOrganizationRoutes(server: Hapi.Server) {
server.route({
method: 'GET',
path: '/admin/organizations',
options: {
pre: [{ method: (req, h) => requirePermission(req, h, 'organization:read') }],
},
handler: async () => organizations.listOrganizations({ limit: 20 }),
});
}4. API key/OAuth protected auth route for machine tokens
import type Hapi from '@hapi/hapi';
import { gateway, organizations } from '@wacht/backend';
export function registerMachineOrganizationRoutes(server: Hapi.Server) {
server.route({
method: 'GET',
path: '/machine/organizations',
handler: async (request, h) => {
const apiKey = request.headers['x-api-key'];
const decision = await gateway.checkPrincipalAuthz({
principalType: 'api_key',
principalValue: typeof apiKey === 'string' ? apiKey : '',
resource: '/machine/organizations',
method: 'GET',
requiredPermissions: ['organization:read'],
});
if (!decision.allowed) {
return h.response({ error: 'forbidden' }).code(403);
}
return organizations.listOrganizations({ limit: 20 });
},
});
}Best practices
- Use
options.prefor guard logic and keep handlers business-only. - Store normalized auth context on
request.app. - In Node.js, publishable key can be resolved from env automatically.
- Use API key/OAuth protected checks for machine-token endpoints.
- Custom
gatewayUrlhost override is available on Enterprise plans. - Keep permission strings centralized for consistency.