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/objectqlRemote procedure calls REST Data API /api/dataCRUD operations on objects Metadata API /api/metadataSchema and metadata information File Operations /api/filesFile 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/123
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/actions
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/abc123
The 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 routes option 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