Custom API Routes Configuration
Custom API Routes Configuration
Overview
ObjectQL allows you to configure custom API route paths during initialization instead of using hardcoded default paths. This feature provides flexibility for:
- API Versioning: Use paths like
/v1/api,/v2/api - Custom Naming: Use domain-specific naming like
/resources,/schema - Multiple API Instances: Run multiple ObjectQL instances with different paths
- Integration Requirements: Align with existing API structures
Default Routes
By default, ObjectQL uses these API paths:
| Endpoint Type | Default Path | Description |
|---|---|---|
| JSON-RPC | /api/objectql | Remote procedure calls |
| REST Data API | /api/data | CRUD operations on objects |
| Metadata API | /api/metadata | Schema and metadata information |
| File Operations | /api/files | File upload and download |
Configuration
Basic Usage
Configure custom routes when creating the Kernel with protocol plugins:
import { ObjectStackKernel } from '@objectstack/runtime';
import { HonoServerPlugin } from '@objectstack/plugin-hono-server';
import { ObjectQLPlugin } from '@objectql/core';
import { JSONRPCPlugin } from '@objectql/protocol-json-rpc';
import { MemoryDriver } from '@objectql/driver-memory';
// Define custom routes via plugin configuration
const kernel = new ObjectStackKernel([
appConfig,
new MemoryDriver(),
new ObjectQLPlugin(),
new JSONRPCPlugin({ basePath: '/v1/rpc' }),
new HonoServerPlugin({
port: 3000,
routes: {
data: '/v1/resources',
metadata: '/v1/schema',
files: '/v1/storage'
}
})
]);
await kernel.start();Route Configuration Interface
interface ApiRouteConfig {
/**
* Base path for JSON-RPC endpoint
* @default '/api/objectql'
*/
rpc?: string;
/**
* Base path for REST data API
* @default '/api/data'
*/
data?: string;
/**
* Base path for metadata API
* @default '/api/metadata'
*/
metadata?: string;
/**
* Base path for file operations
* @default '/api/files'
*/
files?: string;
}Complete Example
import { ObjectStackKernel } from '@objectstack/runtime';
import { HonoServerPlugin } from '@objectstack/plugin-hono-server';
import { ObjectQLPlugin } from '@objectql/core';
import { SqlDriver } from '@objectql/driver-sql';
import { JSONRPCPlugin } from '@objectql/protocol-json-rpc';
async function main() {
// 1. Define app configuration
const appConfig = {
name: 'my-app',
objects: {
user: {
name: 'user',
label: 'User',
fields: {
name: { type: 'text', label: 'Name' },
email: { type: 'email', label: 'Email' }
}
}
}
};
// 2. Create Kernel with custom route configuration
const kernel = new ObjectStackKernel([
appConfig,
new SqlDriver({
client: 'sqlite3',
connection: { filename: ':memory:' },
useNullAsDefault: true
}),
new ObjectQLPlugin(),
new JSONRPCPlugin({ basePath: '/v1/rpc' }),
new HonoServerPlugin({
port: 3000,
routes: {
data: '/v1/resources',
metadata: '/v1/schema',
files: '/v1/storage'
}
})
]);
await kernel.start();
console.log('🚀 Server running with custom routes');
console.log(' JSON-RPC: http://localhost:3000/v1/rpc');
console.log(' REST API: http://localhost:3000/v1/resources');
console.log(' Metadata: http://localhost:3000/v1/schema');
console.log(' Files: http://localhost:3000/v1/storage');
}
main().catch(console.error);Using Custom Routes
JSON-RPC Endpoint
Default: POST /api/objectql
Custom: POST /v1/rpc
curl -X POST http://localhost:3000/v1/rpc \
-H "Content-Type: application/json" \
-d '{
"op": "find",
"object": "user",
"args": {}
}'REST Data API
Default: /api/data/:object
Custom: /v1/resources/:object
# List users
curl http://localhost:3000/v1/resources/user
# Get specific user
curl http://localhost:3000/v1/resources/user/123
# Create user
curl -X POST http://localhost:3000/v1/resources/user \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# Update user
curl -X PUT http://localhost:3000/v1/resources/user/123 \
-H "Content-Type: application/json" \
-d '{"name": "Alice Updated"}'
# Delete user
curl -X DELETE http://localhost:3000/v1/resources/user/123Metadata API
Default: /api/metadata
Custom: /v1/schema
# List all objects
curl http://localhost:3000/v1/schema/objects
# Get object details
curl http://localhost:3000/v1/schema/object/user
# Get field metadata
curl http://localhost:3000/v1/schema/object/user/fields/email
# List object actions
curl http://localhost:3000/v1/schema/object/user/actionsFile Operations
Default: /api/files
Custom: /v1/storage
# Upload file
curl -X POST http://localhost:3000/v1/storage/upload \
-F "file=@myfile.pdf" \
-F "object=document" \
-F "field=attachment"
# Download file
curl http://localhost:3000/v1/storage/abc123Client SDK Configuration
The ObjectQL SDK clients also support custom route configuration:
Data API Client
import { DataApiClient } from '@objectql/sdk';
const client = new DataApiClient({
baseUrl: 'http://localhost:3000',
dataPath: '/v1/resources' // Custom data path
});
const users = await client.list('user');Metadata API Client
import { MetadataApiClient } from '@objectql/sdk';
const client = new MetadataApiClient({
baseUrl: 'http://localhost:3000',
metadataPath: '/v1/schema' // Custom metadata path
});
const objects = await client.listObjects();Remote Driver
import { RemoteDriver } from '@objectql/sdk';
const driver = new RemoteDriver(
'http://localhost:3000',
'/v1/rpc' // Custom RPC path
);Common Use Cases
API Versioning
Support multiple API versions simultaneously:
// API v1
const v1Routes = {
rpc: '/api/v1/rpc',
data: '/api/v1/data',
metadata: '/api/v1/metadata',
files: '/api/v1/files'
};
// API v2
const v2Routes = {
rpc: '/api/v2/rpc',
data: '/api/v2/data',
metadata: '/api/v2/metadata',
files: '/api/v2/files'
};
const v1Handler = createNodeHandler(appV1, { routes: v1Routes });
const v2Handler = createNodeHandler(appV2, { routes: v2Routes });
server.all('/api/v1/*', v1Handler);
server.all('/api/v2/*', v2Handler);Domain-Specific Naming
Use business-friendly terminology:
const businessRoutes = {
rpc: '/business/operations',
data: '/business/entities',
metadata: '/business/definitions',
files: '/business/documents'
};Multi-Tenant Applications
Isolate APIs per tenant:
app.use('/:tenantId/api/*', (req, res, next) => {
const tenantRoutes = {
rpc: `/${req.params.tenantId}/api/rpc`,
data: `/${req.params.tenantId}/api/data`,
metadata: `/${req.params.tenantId}/api/metadata`,
files: `/${req.params.tenantId}/api/files`
};
const handler = createNodeHandler(
getTenantApp(req.params.tenantId),
{ routes: tenantRoutes }
);
handler(req, res);
});Backward Compatibility
All handlers maintain backward compatibility:
- If no
routesoption is provided, default paths are used - Existing applications continue to work without changes
- Migration to custom routes is opt-in
// This still works with default routes
const handler = createNodeHandler(app);
// Uses /api/objectql, /api/data, /api/metadata, /api/filesBest Practices
- Consistency: Use the same route structure across all handlers
- Documentation: Document your custom routes for API consumers
- Versioning: Consider using versioned paths for production APIs
- Testing: Test custom routes thoroughly before deployment
- Migration: Plan gradual migration if changing existing routes