Routing

What is Routing? #

Routing in Orchesty SDK refers to the HTTP endpoint system that allows the Orchesty platform to communicate with your integration code. When the orchestration layer needs to execute a connector or application action, it makes HTTP requests to specific endpoints that the SDK automatically creates and manages.

Key concepts:

  • HTTP endpoints: URLs that trigger your connectors and applications
  • Router classes: Components that define and handle endpoints
  • Express.js: Web framework powering the SDK's HTTP server
  • Endpoint discovery: How Orchesty finds available connectors
  • Request/Response flow: How data moves through HTTP layer

Why Understanding Routing Matters #

Understanding routing helps you:

  • Debug issues: Know where requests come from and where they go
  • Test locally: Call your connectors directly via HTTP
  • Monitor traffic: Understand what's happening in your integration
  • Troubleshoot errors: Trace HTTP-level problems
  • Extend SDK: Add custom endpoints if needed

How Routing Works #

Request Flow #

sequenceDiagram
    participant O as Orchesty Platform
    participant R as Router
    participant C as Connector/Application
    participant A as External API
    
    O->>R: POST /connector/my-connector/action
    R->>R: Parse request, create ProcessDto
    R->>C: Call processAction(dto)
    C->>A: Make API calls
    A-->>C: Return data
    C->>C: Transform data
    C-->>R: Return ProcessDto
    R->>R: Format response
    R-->>O: Return HTTP response

Router Architecture #

graph TB
    Express[Express App]
    
    Express --> CR[ConnectorRouter]
    Express --> AR[ApplicationRouter]
    Express --> BR[BatchRouter]
    Express --> WR[WebhookRouter]
    Express --> CNR[CustomNodeRouter]
    
    CR --> C1[Connector 1]
    CR --> C2[Connector 2]
    AR --> A1[Application 1]
    AR --> A2[Application 2]
    BR --> B1[Batch Node 1]
    
    style Express fill:#e1f5ff
    style CR fill:#fff4e1
    style AR fill:#fff4e1
    style BR fill:#fff4e1

SDK Endpoints #

Connector Endpoints #

Execute Connector #

POST /connector/{name}/action
Content-Type: application/json

{
  "data": {...},
  "headers": {...}
}

Purpose: Execute a specific connector

Example:

curl -X POST http://localhost:8080/connector/get-customer/action \
  -H "Content-Type: application/json" \
  -d '{"customerId": "123"}'

Test Connector #

GET /connector/{name}/action/test

Purpose: Check if connector is registered and loadable

Example:

curl http://localhost:8080/connector/get-customer/action/test

List Connectors #

GET /connector/list

Purpose: Get all registered connectors

Response:

[
  {
    "name": "get-customer",
    "type": "connector"
  },
  {
    "name": "create-order",
    "type": "connector"
  }
]

Application Endpoints #

List Applications #

GET /application/list

Purpose: Get all registered applications

Get Application Details #

GET /application/{name}

Purpose: Get application metadata (name, logo, description)

Get Authorization Form #

GET /application/{name}/form

Purpose: Get the credential form for user to fill out

Authorize Application #

GET /application/{name}/authorize?user={email}

Purpose: Start OAuth2 authorization flow

Check Authorization Status #

GET /application/{name}/users/{email}/authorized

Purpose: Check if user has valid credentials

Test Application #

GET /application/{name}/test

Purpose: Check if application is registered

Batch Endpoints #

Execute Batch Node #

POST /batch/{name}/action

Purpose: Execute a batch processing node

Test Batch Node #

GET /batch/{name}/action/test

Purpose: Check if batch node is registered

List Batch Nodes #

GET /batch/list

Purpose: Get all registered batch nodes

Webhook Endpoints #

Subscribe Webhook #

POST /webhook/applications/{name}/users/{user}/subscribe
Content-Type: application/json

{
  "name": "webhook-name",
  "topology": "topology-id"
}

Purpose: Subscribe to webhook events

Unsubscribe Webhook #

POST /webhook/applications/{name}/users/{user}/unsubscribe

Purpose: Unsubscribe from webhook events

List Webhooks #

GET /webhook/applications/{name}/users/{user}

Purpose: Get user's active webhooks

Custom Node Endpoints #

Execute Custom Node #

POST /custom-node/{name}/action

Purpose: Execute a custom node

List Custom Nodes #

GET /custom-node/list

Purpose: Get all registered custom nodes

Router Classes #

ConnectorRouter #

Handles connector execution:

// From: orchesty-nodejs-sdk/lib/Connector/ConnectorRouter.ts
export default class ConnectorRouter extends ACommonRouter {
    
    public configureRoutes(): express.Application {
        // Execute connector
        this.app.route('/connector/:name/action').post(async (req, res, next) => {
            try {
                const connector = this.loader.get(CONNECTOR_PREFIX, req.params.name);
                const dto = await connector.processAction(
                    await createProcessDto(req, connector.getApplicationName())
                );
                
                createSuccessResponse(res, dto);
                next();
            } catch (e) {
                next(e);
            }
        });
        
        // Test connector
        this.app.route('/connector/:name/action/test').get(async (req, res, next) => {
            try {
                await this.loader.get(CONNECTOR_PREFIX, req.params.name);
                res.json([]);
                next();
            } catch (e) {
                createApiErrorResponse(req, res, e);
            }
        });
        
        // List connectors
        this.app.route('/connector/list').get((req, res, next) => {
            try {
                res.json(this.loader.getList(CONNECTOR_PREFIX));
                next();
            } catch (e) {
                createApiErrorResponse(req, res, e);
            }
        });
        
        return this.app;
    }
}

ApplicationRouter #

Handles application operations:

// Simplified structure
export default class ApplicationRouter extends ACommonRouter {
    
    public configureRoutes(): express.Application {
        // List applications
        this.app.route('/application/list').get(...);
        
        // Get application details
        this.app.route('/application/:name').get(...);
        
        // Get form
        this.app.route('/application/:name/form').get(...);
        
        // Authorize
        this.app.route('/application/:name/authorize').get(...);
        
        // Check authorized
        this.app.route('/application/:name/users/:user/authorized').get(...);
        
        return this.app;
    }
}

BatchRouter #

Handles batch node execution:

export default class BatchRouter extends ACommonRouter {
    
    public configureRoutes(): express.Application {
        // Execute batch
        this.app.route('/batch/:name/action').post(...);
        
        // Test batch
        this.app.route('/batch/:name/action/test').get(...);
        
        // List batches
        this.app.route('/batch/list').get(...);
        
        return this.app;
    }
}

Request Processing #

Creating ProcessDto from Request #

The SDK automatically converts HTTP requests to ProcessDto:

// Incoming HTTP request:
{
  "body": "{\"customerId\":\"123\"}",
  "headers": {
    "content-type": "application/json",
    "correlation-id": "corr-abc-123",
    "node-id": "node-xyz-456"
  }
}

// Becomes ProcessDto:
const dto = new ProcessDto();
dto.setData('{"customerId":"123"}');
dto.setHeaders({
  'content-type': 'application/json',
  'correlation-id': 'corr-abc-123',
  'node-id': 'node-xyz-456'
});

Creating HTTP Response from ProcessDto #

The SDK converts ProcessDto back to HTTP response:

// ProcessDto after processing:
dto.setJsonData({ customer: {...} });
dto.setSuccessProcess('Customer fetched');

// Becomes HTTP response:
{
  "statusCode": 200,
  "headers": {
    "content-type": "application/json",
    "correlation-id": "corr-abc-123"
  },
  "body": {
    "data": "{\"customer\":{...}}",
    "headers": {...},
    "result-code": "success",
    "result-message": "Customer fetched"
  }
}

Middleware #

The SDK uses Express middleware for cross-cutting concerns:

Error Handler #

Catches errors and formats error responses:

// From: ErrorHandler.ts
export default function errorHandler(nodeRepository: NodeRepository) {
    return async (err: Error, req: Request, res: Response, next: NextFunction) => {
        if (err instanceof OnRepeatException) {
            // Handle retry
            dto.setRepeater(err.getInterval(), err.getMaxHops(), err.message);
            createSuccessResponse(res, dto);
        } else if (err instanceof OnStopAndFailException) {
            // Handle stop
            createErrorResponse(res, dto, err);
        } else {
            // Unknown error
            createErrorResponse(res, dto, err);
        }
    };
}

Metrics Handler #

Records metrics for requests:

// From: MetricsHandler.ts
export default function metricsHandler(req: Request, res: Response, next: NextFunction) {
    const startMetrics = Metrics.getCurrentMetrics();
    
    res.on('finish', () => {
        // Send metrics after response
        sendMetrics(req, res, startMetrics);
    });
    
    next();
}

Testing Endpoints Locally #

Testing Connector #

# Start SDK server
npm start

# Test if connector is registered
curl http://localhost:8080/connector/my-connector/action/test

# Execute connector
curl -X POST http://localhost:8080/connector/my-connector/action \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "123",
    "action": "fetch"
  }'

Testing Application #

# List applications
curl http://localhost:8080/application/list

# Get application form
curl http://localhost:8080/application/shopify/form

# Check if authorized
curl http://localhost:8080/application/shopify/users/user@example.com/authorized

Testing Batch Node #

# List batch nodes
curl http://localhost:8080/batch/list

# Execute batch node
curl -X POST http://localhost:8080/batch/get-all-orders/action \
  -H "Content-Type: application/json" \
  -d '{
    "startDate": "2024-01-01",
    "endDate": "2024-12-31"
  }'

Custom Routing #

Adding Custom Endpoints #

You can add custom endpoints by extending the SDK:

import express from 'express';
import ACommonRouter from '@orchesty/nodejs-sdk/lib/Commons/ACommonRouter';

export default class CustomRouter extends ACommonRouter {
    
    public constructor(app: express.Application) {
        super(app, 'CustomRouter');
    }
    
    public configureRoutes(): express.Application {
        // Add custom health check endpoint
        this.app.route('/health').get((req, res) => {
            res.json({
                status: 'healthy',
                timestamp: new Date().toISOString(),
                uptime: process.uptime()
            });
        });
        
        // Add custom metrics endpoint
        this.app.route('/metrics').get((req, res) => {
            res.json({
                connectors: this.getConnectorCount(),
                applications: this.getApplicationCount(),
                memory: process.memoryUsage()
            });
        });
        
        return this.app;
    }
    
    private getConnectorCount(): number {
        // Implementation
        return 0;
    }
    
    private getApplicationCount(): number {
        // Implementation
        return 0;
    }
}

Registering Custom Router #

import DIContainer from '@orchesty/nodejs-sdk/lib/DIContainer/Container';
import CustomRouter from './routers/CustomRouter';

const container = new DIContainer();

// Get Express app
const app = container.getApp();

// Add custom router
new CustomRouter(app);

// Start server
container.listen();

Port Configuration #

Default Port #

The SDK runs on port 8080 by default:

Server listening on port 8080

Custom Port #

Set via environment variable:

PORT=3000 npm start

Or in code:

// Set port before starting
process.env.PORT = '3000';

const container = new DIContainer();
container.listen();

CORS and Security #

CORS Configuration #

For browser-based testing:

import cors from 'cors';

const container = new DIContainer();
const app = container.getApp();

// Enable CORS
app.use(cors({
    origin: 'http://localhost:3000',
    credentials: true
}));

container.listen();

Authentication #

The SDK doesn't enforce authentication at the HTTP level. Authentication is handled:

  • For users: Via ApplicationInstall credentials
  • For Orchesty: Via API keys in headers
  • For webhooks: Via secure tokens in URLs

Monitoring and Debugging #

Request Logging #

All requests are automatically logged:

{
  "message": "POST /connector/get-customer/action",
  "statusCode": 200,
  "duration": 234,
  "correlationId": "corr-abc-123"
}

Error Responses #

Errors are formatted consistently:

{
  "statusCode": 500,
  "error": "Internal Server Error",
  "message": "Failed to fetch customer: Network timeout",
  "correlationId": "corr-abc-123"
}

Health Checks #

Check if SDK is running:

# Any endpoint will work, but /connector/list is lightweight
curl http://localhost:8080/connector/list

Common Routing Patterns #

Pattern: Authentication Middleware #

import express from 'express';

function requireApiKey(req: express.Request, res: express.Response, next: express.NextFunction) {
    const apiKey = req.headers['x-api-key'];
    
    if (!apiKey || apiKey !== process.env.API_KEY) {
        res.status(401).json({ error: 'Unauthorized' });
        return;
    }
    
    next();
}

// Apply to specific routes
app.use('/connector', requireApiKey);

Pattern: Rate Limiting Middleware #

import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
    windowMs: 60 * 1000,  // 1 minute
    max: 100,              // 100 requests per minute
    message: 'Too many requests from this IP'
});

app.use('/connector', limiter);

Pattern: Request Validation #

function validateRequest(req: express.Request, res: express.Response, next: express.NextFunction) {
    if (!req.body || typeof req.body !== 'object') {
        res.status(400).json({ error: 'Invalid request body' });
        return;
    }
    
    next();
}

app.use('/connector/:name/action', validateRequest);

Troubleshooting #

Endpoint Not Found (404) #

Check:

  1. Is the connector/application registered in DIContainer?
  2. Is the name correct in the URL?
  3. Is the SDK server running?
  4. Check /connector/list to see registered connectors

Internal Server Error (500) #

Check:

  1. Application logs for error messages
  2. Ensure connector/application code doesn't throw unhandled errors
  3. Verify all dependencies are available

Timeout Errors #

Check:

  1. Connector execution time (default timeout: 30s)
  2. External API response times
  3. Database query performance

CORS Errors #

Add CORS middleware (see CORS Configuration above)

Best Practices #

1. Use Descriptive Names #

// Good
this.app.route('/connector/:name/action')
this.app.route('/application/:name/authorize')

// Bad
this.app.route('/c/:n/a')
this.app.route('/app/:name/auth')

2. Handle Errors Properly #

this.app.route('/connector/:name/action').post(async (req, res, next) => {
    try {
        // Process request
    } catch (error) {
        next(error);  // Pass to error handler
    }
});

3. Return Consistent Responses #

// Success
res.json({ 
    statusCode: 200,
    data: result 
});

// Error
res.status(500).json({ 
    statusCode: 500,
    error: 'Error message'
});

4. Log Requests #

import logger from '@orchesty/nodejs-sdk/lib/Logger/Logger';

this.app.route('/custom-endpoint').get((req, res) => {
    logger.info(`Custom endpoint called: ${req.path}`, req);
    // Handle request
});

5. Test Endpoints #

Always test your endpoints locally before deploying:

# Test connector
curl -v http://localhost:8080/connector/my-connector/action/test

# Test with data
curl -v -X POST http://localhost:8080/connector/my-connector/action \
  -H "Content-Type: application/json" \
  -d '{"test": "data"}'

API References #

Next Steps #

  1. Learn about Connectors to understand what endpoints execute
  2. Explore Data Flow to see how ProcessDto works
  3. Read about Logging for monitoring requests
  4. Check DIContainer docs for SDK initialization
© 2025 Orchesty Solutions. All rights reserved.