route-handler-authoring
Conventions for writing Hono route handlers that forward validated bodies to DAL functions, and CRUD test requirements for round-trip field persistence. Triggers on: creating new routes, modifying handlers, adding CRUD tests, route handler patterns, field-picking, spread pattern, DAL forwarding, check:route-handler-patterns, CI check failure.
Best use case
route-handler-authoring is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Conventions for writing Hono route handlers that forward validated bodies to DAL functions, and CRUD test requirements for round-trip field persistence. Triggers on: creating new routes, modifying handlers, adding CRUD tests, route handler patterns, field-picking, spread pattern, DAL forwarding, check:route-handler-patterns, CI check failure.
Teams using route-handler-authoring should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/route-handler-authoring/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How route-handler-authoring Compares
| Feature / Agent | route-handler-authoring | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Conventions for writing Hono route handlers that forward validated bodies to DAL functions, and CRUD test requirements for round-trip field persistence. Triggers on: creating new routes, modifying handlers, adding CRUD tests, route handler patterns, field-picking, spread pattern, DAL forwarding, check:route-handler-patterns, CI check failure.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# Route Handler Authoring Guide
Conventions for writing route handlers in `agents-api/src/domains/**/routes/` and their corresponding CRUD tests.
---
## Spread Pattern (Required)
When forwarding a validated request body to a DAL function, **always spread the body**. Never use explicit field-picking.
### Correct Pattern
```typescript
const body = c.req.valid('json');
const result = await createEntity(db)({
...body,
id: body.id || generateId(),
});
```
Spread the full body first, then override specific fields with transformations.
### Incorrect Pattern (Anti-Pattern)
```typescript
const body = c.req.valid('json');
const result = await createEntity(db)({
name: body.name,
description: body.description,
config: body.config,
id: body.id || generateId(),
});
```
Explicit field-picking silently drops any field not listed. When new columns are added to the schema, the handler will not forward them — causing data loss bugs that are invisible until discovered in production.
---
## Field Transformation Overrides
Some fields require transformation before storage. Place these **after** the spread so they override the raw values:
```typescript
const body = c.req.valid('json');
const result = await createTrigger(db)({
...body,
id: generateId(),
hashedAuthentication: body.authentication
? await hashAuthentication(body.authentication)
: null,
createdBy: c.get('userId'),
});
```
Common transformations to preserve as overrides:
- ID generation: `id: body.id || generateId()`
- Null coercion: `expiresAt: body.expiresAt || undefined`
- Type coercion: `position: String(body.position)`
- Default values: `enabled: body.enabled ?? true`
- Authentication hashing: `hashedAuthentication: await hashAuthentication(...)`
- Computed fields: `createdBy: c.get('userId')`
---
## CI Enforcement
The `scripts/check-route-handler-patterns.mjs` script runs in CI as part of `pnpm check`. It detects handlers that call `c.req.valid('json')` and access the body variable via explicit field-picking without a corresponding spread.
### Allowlisting Exceptions
If a handler legitimately needs explicit field-picking (rare), add the comment:
```typescript
// allow-field-picking
```
within the object literal block that requires the exception. Use this sparingly — most handlers should use the spread pattern.
### Running Locally
```bash
pnpm check:route-handler-patterns
```
---
## CRUD Test Requirements
Every entity with a route handler must have round-trip field persistence tests covering ALL schema fields.
### Required Test Coverage
1. **Create with all fields** → GET → verify all fields match
2. **Update each field individually** → GET → verify updated value
3. **Round-trip with all optional fields simultaneously** → verify all persist
4. **Null/undefined handling** for optional fields
5. **Field clearing** (set to null) for nullable fields
6. **Default values** are applied and returned correctly
### Test File Location
Tests live in `agents-api/src/__tests__/manage/routes/crud/` and use `makeRequest()` from `agents-api/src/__tests__/utils/testRequest.ts`.
### Exemplary Test Files
Use these as patterns for comprehensive field coverage:
- `contextConfigs.test.ts` — demonstrates full round-trip testing for all schema fields
- `credentialReferences.test.ts` — demonstrates create/update/read cycle for every field
### Test Data Factories
Use helpers from `agents-api/src/__tests__/utils/testHelpers.ts`:
- `createTestAgentData()` — agent entity test data
- `createTestToolData()` — tool entity test data
- Additional helpers for other entity types
### Example Test Pattern
```typescript
it('should persist imageUrl field on create', async () => {
const toolData = createTestToolData({
imageUrl: 'https://example.com/icon.png',
});
const createRes = await makeRequest('POST', '/tools', toolData);
expect(createRes.status).toBe(200);
const getRes = await makeRequest('GET', `/tools/${createRes.body.id}`);
expect(getRes.status).toBe(200);
expect(getRes.body.imageUrl).toBe('https://example.com/icon.png');
});
it('should update imageUrl field', async () => {
const createRes = await makeRequest('POST', '/tools', createTestToolData());
const updateRes = await makeRequest('PATCH', `/tools/${createRes.body.id}`, {
imageUrl: 'https://example.com/new-icon.png',
});
expect(updateRes.status).toBe(200);
const getRes = await makeRequest('GET', `/tools/${updateRes.body.id}`);
expect(getRes.body.imageUrl).toBe('https://example.com/new-icon.png');
});
```
---
## Route Definition Pattern
All routes must use `createProtectedRoute()` with explicit authorization. See the `createProtectedRoute` section in CLAUDE.md for details on permission helpers.
```typescript
import { createProtectedRoute } from '@inkeep/agents-core/middleware';
import { requireProjectPermission } from '../../middleware/projectAccess';
app.openapi(
createProtectedRoute({
method: 'post',
path: '/',
permission: requireProjectPermission('edit'),
request: { body: { content: { 'application/json': { schema: CreateEntitySchema } } } },
responses: { 200: { content: { 'application/json': { schema: EntitySchema } } } },
}),
async (c) => {
const body = c.req.valid('json');
const result = await createEntity(db)({ ...body, id: generateId() });
return c.json(result);
},
);
```
---
## HTTP Method Conventions
Use the correct HTTP method for each CRUD operation. See the "CRUD HTTP Method Conventions" section in AGENTS.md for the full table and RFC references.
- **PATCH** is the canonical method for partial/sparse update operations
- **PUT** is reserved for full-resource replacement — existing PUT routes remain but new update routes must use PATCH
- When adding a PATCH route alongside an existing PUT route, extract the handler and route config to shared variables, register PATCH with the canonical `operationId`, and register PUT with a `-put` suffixed `operationId` and `'x-speakeasy-ignore': true`Related Skills
weather-safety-guardrails
Keep activity suggestions safe and respect local conditions
structured-itinerary-responses
Present time-aware itineraries with clear actions and citations
write-docs
Write or update documentation for the Inkeep docs site (agents-docs package). Use when: creating new docs, modifying existing docs, introducing features that need documentation, touching MDX files in agents-docs/content/. Triggers on: docs, documentation, MDX, agents-docs, write docs, update docs, add page, new tutorial, API reference, integration guide.
web-design-guidelines
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
vercel-react-best-practices
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
vercel-composition-patterns
React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.
slack-manifest
Guide for modifying the Slack app manifest — adding/removing bot scopes, event subscriptions, slash commands, shortcuts, or OAuth config. Ensures single-source-of-truth via slack-app-manifest.json. Triggers on: slack scope, bot scope, slack manifest, slack permission, add slack scope, remove slack scope, slack event subscription, slash command, slack OAuth, slack-app-manifest.
shadcn
Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
product-surface-areas
Consolidated dependency graph of Inkeep customer-facing surface areas (UIs, CLIs, SDKs, APIs, protocols, config formats). Example use: as a prd-time (planning/brainstorming phase) or post-change checklist to understand the full scope of side-effects or what making one change to the product means for the rest. Use whenever you need to understand the "ripple" out effects of any change.
pr-review-appsec-vendored
Stack-specific application security checklist for this repo's frameworks: better-auth, SpiceDB/AuthZed, and Next.js RSC. Extends the generalizable pr-review-appsec agent with patterns that require framework-specific knowledge to detect. Loaded by pr-review-appsec.
next-upgrade
Upgrade Next.js to the latest version following official migration guides and codemods
next-cache-components
Next.js 16 Cache Components - PPR, use cache directive, cacheLife, cacheTag, updateTag