Agent Skill
2/7/2026api-route
This skill should be used when the user asks to "create an API route", "add an endpoint", "build a backend route", "create an API endpoint", "add a Hono route", or mentions creating REST endpoints, API handlers, or backend routes. Provides Hono API patterns for CoRATES workers.
I
infinitybowman
1GitHub Stars
1Views
npx skills add InfinityBowman/corates
SKILL.md
| Name | api-route |
| Description | This skill should be used when the user asks to "create an API route", "add an endpoint", "build a backend route", "create an API endpoint", "add a Hono route", or mentions creating REST endpoints, API handlers, or backend routes. Provides Hono API patterns for CoRATES workers. |
name: API Route description: This skill should be used when the user asks to "create an API route", "add an endpoint", "build a backend route", "create an API endpoint", "add a Hono route", or mentions creating REST endpoints, API handlers, or backend routes. Provides Hono API patterns for CoRATES workers.
API Route Creation
Create Hono API routes following CoRATES workers patterns.
Core Principles
- Middleware composition - Chain auth, permissions, validation middleware
- Domain errors - Use createDomainError from @corates/shared
- Zod validation - Define schemas centrally in config/validation.js
- Drizzle ORM - All database operations through Drizzle
- Context isolation - Attach data to Hono context, read via getters
Quick Reference
File Location
packages/workers/src/
routes/
[feature].js # Standalone routes
[feature]/
index.js # Route composition
[subroute].js # Nested routes
config/
validation.js # Zod schemas
middleware/
auth.js # Authentication
requireOrg.js # Org membership
Basic Route Template
import { Hono } from 'hono';
import { eq, and } from 'drizzle-orm';
import { createDb } from '@/db/client.js';
import { items } from '@/db/schema.js';
import { requireAuth, getAuth } from '@/middleware/auth.js';
import { validateRequest } from '@/config/validation.js';
import { createDomainError, ITEM_ERRORS, SYSTEM_ERRORS } from '@corates/shared';
import { itemSchemas } from '@/config/validation.js';
const itemRoutes = new Hono();
// Apply auth to all routes
itemRoutes.use('*', requireAuth);
// GET - List items
itemRoutes.get('/', async c => {
const { user } = getAuth(c);
const db = createDb(c.env.DB);
try {
const results = await db.select().from(items).where(eq(items.userId, user.id));
return c.json(results);
} catch (error) {
console.error('Error fetching items:', error);
const dbError = createDomainError(SYSTEM_ERRORS.DB_ERROR, {
operation: 'fetch_items',
});
return c.json(dbError, dbError.statusCode);
}
});
// POST - Create item
itemRoutes.post('/', validateRequest(itemSchemas.create), async c => {
const { user } = getAuth(c);
const { name, description } = c.get('validatedBody');
const db = createDb(c.env.DB);
try {
const id = crypto.randomUUID();
const now = new Date();
await db.insert(items).values({
id,
name,
description,
userId: user.id,
createdAt: now,
});
return c.json({ id, name, description }, 201);
} catch (error) {
console.error('Error creating item:', error);
const dbError = createDomainError(SYSTEM_ERRORS.DB_ERROR, {
operation: 'create_item',
});
return c.json(dbError, dbError.statusCode);
}
});
export { itemRoutes };
Middleware Chain
Apply middleware in order of specificity:
import { requireAuth, getAuth } from '@/middleware/auth.js';
import { requireOrgMembership, getOrgContext } from '@/middleware/requireOrg.js';
import { requireOrgWriteAccess } from '@/middleware/requireOrgWriteAccess.js';
import { requireEntitlement } from '@/middleware/requireEntitlement.js';
import { requireQuota } from '@/middleware/requireQuota.js';
import { validateRequest } from '@/config/validation.js';
// Full middleware chain for protected route
routes.post(
'/',
requireOrgMembership(), // Check org membership
requireOrgWriteAccess(), // Check write permission
requireEntitlement('feature.x'), // Check feature access
requireQuota('items.max', getItemCount, 1), // Check quota
validateRequest(schema), // Validate body
async c => {
const { user } = getAuth(c);
const { orgId } = getOrgContext(c);
const data = c.get('validatedBody');
// Handler code...
},
);
Context Getters
// Authentication
const { user, session } = getAuth(c);
// Organization context (after requireOrgMembership)
const { orgId, orgRole, org } = getOrgContext(c);
// Validated request body (after validateRequest)
const data = c.get('validatedBody');
// Billing/entitlements (after requireEntitlement)
const entitlements = c.get('entitlements');
const quotas = c.get('quotas');
Zod Validation
Define Schemas
Add to packages/workers/src/config/validation.js:
export const itemSchemas = {
create: z.object({
name: z
.string()
.min(1, 'Name is required')
.max(255)
.transform(val => val.trim()),
description: z
.string()
.max(2000)
.optional()
.transform(val => val?.trim() || null),
}),
update: z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(2000).optional(),
}),
};
Common Patterns
// Required string with trim
name: z.string().min(1).max(255).transform(val => val.trim()),
// Optional with null fallback
description: z.string().max(2000).optional().transform(val => val?.trim() || null),
// Email
email: z.string().email('Invalid email'),
// UUID
id: z.string().uuid(),
// Enum
role: z.enum(['owner', 'admin', 'member', 'viewer']),
// Boolean with default
active: z.boolean().optional().default(true),
Error Handling
Domain Errors
import { createDomainError, PROJECT_ERRORS, AUTH_ERRORS, SYSTEM_ERRORS } from '@corates/shared';
// Not found
const error = createDomainError(PROJECT_ERRORS.NOT_FOUND, { projectId });
return c.json(error, error.statusCode);
// Forbidden with reason
const error = createDomainError(AUTH_ERRORS.FORBIDDEN, {
reason: 'insufficient_role',
required: 'admin',
});
return c.json(error, error.statusCode);
// Database error
const dbError = createDomainError(SYSTEM_ERRORS.DB_ERROR, {
operation: 'create_item',
originalError: error.message,
});
return c.json(dbError, dbError.statusCode);
Try-Catch Pattern
routes.get('/:id', async c => {
const { user } = getAuth(c);
const id = c.req.param('id');
const db = createDb(c.env.DB);
try {
const result = await db
.select()
.from(items)
.where(and(eq(items.id, id), eq(items.userId, user.id)))
.get();
if (!result) {
const error = createDomainError(ITEM_ERRORS.NOT_FOUND, { id });
return c.json(error, error.statusCode);
}
return c.json(result);
} catch (error) {
console.error('Error fetching item:', error);
const dbError = createDomainError(SYSTEM_ERRORS.DB_ERROR, {
operation: 'fetch_item',
});
return c.json(dbError, dbError.statusCode);
}
});
Database Operations
Query Patterns
import { createDb } from '@/db/client.js';
import { eq, and, or, like, desc, sql } from 'drizzle-orm';
const db = createDb(c.env.DB);
// Select with join
const results = await db
.select({
id: projects.id,
name: projects.name,
role: projectMembers.role,
})
.from(projects)
.innerJoin(projectMembers, eq(projects.id, projectMembers.projectId))
.where(eq(projectMembers.userId, user.id))
.orderBy(desc(projects.updatedAt));
// Single record
const item = await db
.select()
.from(items)
.where(eq(items.id, id))
.get();
// Insert
await db.insert(items).values({ id, name, createdAt: now });
// Update
await db.update(items)
.set({ name, updatedAt: now })
.where(eq(items.id, id));
// Delete
await db.delete(items).where(eq(items.id, id));
// Batch for atomic operations
await db.batch([
db.insert(projects).values({ ... }),
db.insert(projectMembers).values({ ... }),
]);
Response Patterns
// Success with data
return c.json(result);
// Created (201)
return c.json(newItem, 201);
// Success flag
return c.json({ success: true, id: itemId });
// Array response
return c.json(results);
// Error response
return c.json(error, error.statusCode);
Route Registration
Mount in Main App
// packages/workers/src/index.js
import { itemRoutes } from './routes/items.js';
app.route('/api/items', itemRoutes);
Nested Routes
// In parent route file
import { subRoutes } from './subroute.js';
parentRoutes.route('/:parentId/children', subRoutes);
// Creates: /api/parent/:parentId/children/...
Creation Checklist
When creating an API route:
- Create route file in
packages/workers/src/routes/ - Define Zod schemas in
config/validation.js - Apply appropriate middleware chain
- Use context getters for auth/org/validated data
- Use Drizzle for all database operations
- Return domain errors with proper status codes
- Register route in
index.js
Additional Resources
Reference Files
For detailed patterns:
references/patterns.md- Middleware details, complex queries, nested routesreferences/examples.md- Real route examples from the codebase
Example Files
Working templates in examples/:
ExampleRoutes.js- Complete CRUD route template
Skills Info
Original Name:api-routeAuthor:infinitybowman
Download