Learn how to configure custom API routes in ObjectQL. This guide covers route definitions, custom endpoints, middleware integration, and extending the default API with your own handlers.
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
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 |
Configure custom routes when creating handlers:
import { createNodeHandler, createRESTHandler, createMetadataHandler } from '@objectql/server';
// Define custom routes
const customRoutes = {
rpc: '/v1/rpc',
data: '/v1/resources',
metadata: '/v1/schema',
files: '/v1/storage'
};
// Create handlers with custom routes
const nodeHandler = createNodeHandler(app, { routes: customRoutes });
const restHandler = createRESTHandler(app, { routes: customRoutes });
const metadataHandler = createMetadataHandler(app, { routes: customRoutes });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;
}import express from 'express';
import { ObjectQL } from '@objectql/core';
import { SqlDriver } from '@objectql/driver-sql';
import { createNodeHandler, createRESTHandler, createMetadataHandler } from '@objectql/server';
async function main() {
// 1. Initialize ObjectQL
const app = new ObjectQL({
datasources: {
default: new SqlDriver({
client: 'sqlite3',
connection: { filename: ':memory:' },
useNullAsDefault: true
})
}
});
// Register your objects
app.registerObject({
name: 'user',
label: 'User',
fields: {
name: { type: 'text', label: 'Name' },
email: { type: 'email', label: 'Email' }
}
});
await app.init();
// 2. Define custom API routes
const customRoutes = {
rpc: '/v1/rpc',
data: '/v1/resources',
metadata: '/v1/schema',
files: '/v1/storage'
};
// 3. Create handlers with custom routes
const nodeHandler = createNodeHandler(app, { routes: customRoutes });
const restHandler = createRESTHandler(app, { routes: customRoutes });
const metadataHandler = createMetadataHandler(app, { routes: customRoutes });
// 4. Setup Express with custom paths
const server = express();
server.all('/v1/rpc*', nodeHandler);
server.all('/v1/resources/*', restHandler);
server.all('/v1/schema*', metadataHandler);
server.listen(3000, () => {
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);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": {}
}'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/123Default: /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/actionsDefault: /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/abc123The ObjectQL SDK clients also support custom route configuration:
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');import { MetadataApiClient } from '@objectql/sdk';
const client = new MetadataApiClient({
baseUrl: 'http://localhost:3000',
metadataPath: '/v1/schema' // Custom metadata path
});
const objects = await client.listObjects();import { RemoteDriver } from '@objectql/sdk';
const driver = new RemoteDriver(
'http://localhost:3000',
'/v1/rpc' // Custom RPC path
);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);Use business-friendly terminology:
const businessRoutes = {
rpc: '/business/operations',
data: '/business/entities',
metadata: '/business/definitions',
files: '/business/documents'
};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);
});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/files- 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