β

ObjectQL v4.0 is currently in Beta.

ObjectStack LogoObjectQL
API ReferenceAPI Reference

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 TypeDefault PathDescription
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

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/123

Metadata 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/actions

File 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/abc123

Client 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 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

Best Practices

  1. Consistency: Use the same route structure across all handlers
  2. Documentation: Document your custom routes for API consumers
  3. Versioning: Consider using versioned paths for production APIs
  4. Testing: Test custom routes thoroughly before deployment
  5. Migration: Plan gradual migration if changing existing routes

On this page