REST Adapter Configuration
The REST adapter converts procedure definitions into HTTP endpoints. Configure its behavior with adapter options.
Basic Setup
Section titled “Basic Setup”import { rest } from '@veloxts/router';
app.register( rest([userProcedures, postProcedures], { // Options go here }), { prefix: '/api' } // Fastify prefix option);Configuration Options
Section titled “Configuration Options”prefix
Section titled “prefix”API prefix for all routes. When using server.register(), Fastify’s built-in prefix is recommended:
// RECOMMENDED: Use Fastify's prefix optionapp.register( rest([userProcedures]), { prefix: '/api' });// → GET /api/users
// LEGACY: Adapter prefix (for direct invocation)rest([userProcedures], { prefix: '/v1' })(app);// → GET /v1/usersDefault: /api (legacy mode only)
shortcuts
Section titled “shortcuts”Generate shortcut routes alongside nested routes for easier access to deeply nested resources.
app.register( rest([taskProcedures], { shortcuts: true, }), { prefix: '/api' });Effect:
// With shortcuts: true, generates BOTH:GET /organizations/:orgId/projects/:projectId/tasks/:id (nested)GET /tasks/:id (shortcut)
// Collection routes remain nested-onlyGET /organizations/:orgId/projects/:projectId/tasks (nested only)Limitations:
- Only works for single-resource operations (routes ending with
/:id) - Collection operations (
list*,find*) and creation (create*,add*) require parent context
Default: false
nestingWarnings
Section titled “nestingWarnings”Enable or disable warnings about deep nesting (3+ levels).
app.register( rest([featureProcedures], { shortcuts: true, nestingWarnings: false, // Disable warnings }), { prefix: '/api' });Effect:
// With nestingWarnings: true (default):// ⚠️ Resource 'features/getFeature' has 4 levels of nesting.// Consider using shortcuts: true or restructuring your API.
// With nestingWarnings: false:// (no warnings)Default: true
onError
Section titled “onError”Custom error handler for REST endpoints.
app.register( rest([userProcedures], { onError: (error, request, reply) => { console.error('REST error:', error);
// Custom error response reply.status(500).send({ error: 'Internal Server Error', message: error instanceof Error ? error.message : 'Unknown error', }); }, }), { prefix: '/api' });Default: Uses Fastify’s default error handling
Complete Example
Section titled “Complete Example”import { rest, procedures, procedure } from '@veloxts/router';import { z } from 'zod';
// Organizations at rootconst organizationProcedures = procedures('organizations', { listOrganizations: procedure() .query(async ({ ctx }) => { return await ctx.db.organization.findMany(); }),
getOrganization: procedure() .input(z.object({ id: z.string() })) .query(async ({ input, ctx }) => { return await ctx.db.organization.findUniqueOrThrow({ where: { id: input.id }, }); }),});
// Projects nested under organizationsconst projectProcedures = procedures('projects', { listProjects: procedure() .parent('organizations') .input(z.object({ organizationId: z.string() })) .query(async ({ input, ctx }) => { return await ctx.db.project.findMany({ where: { organizationId: input.organizationId }, }); }),});
// Tasks nested under organizations AND projectsconst taskProcedures = procedures('tasks', { listTasks: procedure() .parents([ { resource: 'organizations', param: 'orgId' }, { resource: 'projects', param: 'projectId' }, ]) .input(z.object({ orgId: z.string(), projectId: z.string() })) .query(async ({ input, ctx }) => { return await ctx.db.task.findMany({ where: { projectId: input.projectId }, }); }),
getTask: procedure() .parents([ { resource: 'organizations', param: 'orgId' }, { resource: 'projects', param: 'projectId' }, ]) .input(z.object({ orgId: z.string().optional(), projectId: z.string().optional(), id: z.string(), })) .query(async ({ input, ctx }) => { return await ctx.db.task.findUniqueOrThrow({ where: { id: input.id }, }); }),});
// Register with full configurationapp.register( rest( [organizationProcedures, projectProcedures, taskProcedures], { shortcuts: true, // Enable shortcut routes for direct resource access nestingWarnings: true, // Keep warnings enabled (default) onError: (error, request, reply) => { // Custom error handling console.error('REST API error:', { url: request.url, method: request.method, error: error instanceof Error ? error.message : error, });
reply.status(500).send({ error: 'Internal Server Error', timestamp: new Date().toISOString(), }); }, } ), { prefix: '/api' });This configuration generates:
GET /api/organizationsGET /api/organizations/:idGET /api/organizations/:orgId/projectsGET /api/organizations/:orgId/projects/:projectId/tasksGET /api/organizations/:orgId/projects/:projectId/tasks/:idGET /api/tasks/:id (shortcut route)Multiple REST Adapters
Section titled “Multiple REST Adapters”You can register multiple REST adapters with different configurations:
// Public API - standard nestingapp.register( rest([publicProcedures], { shortcuts: false, }), { prefix: '/api/v1' });
// Admin API - deep nesting with shortcutsapp.register( rest([adminProcedures], { shortcuts: true, nestingWarnings: false, // Suppress warnings (intentional deep nesting) }), { prefix: '/api/admin' });
// Internal API - tRPC-only (no REST routes needed)// Procedures without recognized naming prefixes (get*, list*, create*, etc.)// simply won't generate REST endpoints.Each adapter instance is independent and can have its own configuration.
Environment-Based Configuration
Section titled “Environment-Based Configuration”Adjust configuration based on environment:
const isDevelopment = process.env.NODE_ENV === 'development';const isProduction = process.env.NODE_ENV === 'production';
app.register( rest([allProcedures], { shortcuts: true, nestingWarnings: !isProduction, // Show warnings in dev, suppress in prod onError: isDevelopment ? (error, request, reply) => { // Detailed errors in development reply.status(500).send({ error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined, url: request.url, method: request.method, }); } : undefined, // Use default handler in production }), { prefix: '/api' });Best Practices
Section titled “Best Practices”Prefer Fastify Prefix
Section titled “Prefer Fastify Prefix”Use Fastify’s built-in prefix option instead of adapter prefix:
// GOOD - Fastify prefixapp.register(rest([procs]), { prefix: '/api' });
// AVOID - Adapter prefix (legacy)rest([procs], { prefix: '/api' })(app);Fastify prefix integrates better with plugins and middleware.
Enable Shortcuts for Deep Nesting
Section titled “Enable Shortcuts for Deep Nesting”If your API exceeds 2 levels of nesting, enable shortcuts:
rest([nestedProcedures], { shortcuts: true });This provides both hierarchical context AND convenient direct access via shortcut routes.
Keep Nesting Shallow
Section titled “Keep Nesting Shallow”Aim for 2-3 levels maximum. If you need more, consider:
- Using query parameters for filtering
- Flattening your resource hierarchy
- Breaking into separate API endpoints
Disable Warnings Only When Justified
Section titled “Disable Warnings Only When Justified”Only set nestingWarnings: false when:
- Deep nesting is intentional and cannot be avoided
- You’ve enabled
shortcutsto mitigate complexity - Your API design has been thoroughly reviewed
Don’t disable warnings just to silence them during development.
Use Global Error Handling
Section titled “Use Global Error Handling”Prefer Fastify’s global error handler over per-adapter handlers:
// GOOD - Global handlerapp.setErrorHandler((error, request, reply) => { // Handle all errors consistently});
// AVOID - Per-adapter handler (unless truly needed)rest([procs], { onError: (error, request, reply) => { /* ... */ },});Troubleshooting
Section titled “Troubleshooting”Routes not generated
Section titled “Routes not generated”Cause: Procedures don’t match naming conventions and lack .rest() overrides
Fix: Check console for warnings, verify naming patterns, or add .rest() configuration
Shortcut routes conflict with root routes
Section titled “Shortcut routes conflict with root routes”Cause: Shortcut route path collides with root resource
Example:
// Conflict: /tasks/:id exists as both root and shortcut routeprocedures('tasks', { getTask: ..., // → GET /tasks/:id (root)});
procedures('nestedTasks', { getNestedTask: procedure() .parent('projects') .query(...), // → GET /tasks/:id (shortcut - CONFLICT!)});Fix: Rename resources to avoid collision or disable shortcuts
Warnings still appearing after disabling
Section titled “Warnings still appearing after disabling”Cause: Multiple adapter registrations, only some have nestingWarnings: false
Fix: Ensure all REST adapter registrations use nestingWarnings: false
Related Content
Section titled “Related Content”- Nested Routes - Hierarchical resource modeling
- REST Conventions - Naming patterns
- REST Overrides - Manual route configuration
- OpenAPI - API documentation generation