π Running Workflows
This guide explains how to trigger Loopstack workflows programmatically from within your NestJS application, enabling you to integrate automation runs into your existing API endpoints, scheduled jobs, or event handlers.
Overview
While workflows can be manually triggered through the Loopstack Studio interface, you often need to start automation runs programmatically in response to:
- External API requests
- Webhook events
- Scheduled cron jobs
- Internal application events
- Batch processing tasks
Loopstack provides the RunService to execute workflows programmatically from anywhere in your NestJS application.
Example
Step 1: Create a Controller
Create a new controller or use an existing one to add an endpoint that will trigger your workflow:
src/app.controller.ts:
import { Controller, Post, Body } from '@nestjs/common';
import { Public } from '@loopstack/shared';
import { RunService } from '@loopstack/core';
import { MyWorkflow } from './my-module/my-workflow.workflow';
import { MyWorkspace } from './my-module/my-workspace';
@Controller()
export class AppController {
constructor(private readonly runService: RunService) {}
@Public()
@Post('run-my-workflow')
async runMyWorkflow(@Body() payload: any) {
// Define the user ID for whom the automation should be executed
const userId = 'user-123';
await this.runService.run(
MyWorkflow.name,
MyWorkspace.name,
payload,
userId,
);
return { message: 'Automation run is queued.' };
}
}In this file:
- We inject the
RunServicethrough the constructor - We create a POST endpoint that accepts a payload
- We call
runService.run()with the workflow name, workspace name, initial payload, and user ID - We use
@Public()decorator to make this endpoint accessible without authentication (remove this if you need authentication)
Step 2: Understanding the RunService Parameters
The runService.run() method accepts four parameters:
await this.runService.run(
workflowName: string, // The class name of your workflow
workspaceName: string, // The class name of your workspace
payload: any, // Data to pass to the workflow
userId: string, // The user ID for workflow execution context
);Advanced Usage Examples
Example 1: Webhook Handler
Trigger a workflow when receiving a webhook from an external service:
import { Controller, Post, Body, Headers } from '@nestjs/common';
import { Public } from '@loopstack/shared';
import { RunService } from '@loopstack/core';
import { ProcessWebhookWorkflow } from './workflows/process-webhook.workflow';
import { MainWorkspace } from './main-workspace';
@Controller('webhooks')
export class WebhookController {
constructor(private readonly runService: RunService) {}
@Public()
@Post('stripe')
async handleStripeWebhook(
@Body() webhookData: any,
@Headers('stripe-signature') signature: string,
) {
// Verify webhook signature here (implementation not shown)
await this.runService.run(
ProcessWebhookWorkflow.name,
MainWorkspace.name,
{
source: 'stripe',
event: webhookData,
signature: signature,
receivedAt: new Date().toISOString(),
},
webhookData.userId,
);
return { received: true };
}
}Example 2: Scheduled Task
Execute a workflow on a schedule using NestJSβs @Cron decorator:
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { RunService } from '@loopstack/core';
import { DailyReportWorkflow } from './workflows/daily-report.workflow';
import { ReportsWorkspace } from './reports-workspace';
@Injectable()
export class TaskScheduler {
constructor(private readonly runService: RunService) {}
@Cron(CronExpression.EVERY_DAY_AT_9AM)
async generateDailyReports() {
// Get list of users who need reports
const users = await this.getUsersWithReportsEnabled();
for (const user of users) {
await this.runService.run(
DailyReportWorkflow.name,
ReportsWorkspace.name,
{
reportDate: new Date().toISOString(),
reportType: 'daily',
},
user.id,
);
}
}
private async getUsersWithReportsEnabled() {
// Implementation to fetch users
return [];
}
}Example 3: Processing Form Submissions
Trigger a workflow when a user submits a form:
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CurrentUser } from './decorators/current-user.decorator';
import { RunService } from '@loopstack/core';
import { ProcessApplicationWorkflow } from './workflows/process-application.workflow';
import { ApplicationsWorkspace } from './applications-workspace';
interface ApplicationFormData {
firstName: string;
lastName: string;
email: string;
resume: string;
coverLetter: string;
}
@Controller('applications')
export class ApplicationsController {
constructor(private readonly runService: RunService) {}
@UseGuards(AuthGuard('jwt'))
@Post('submit')
async submitApplication(
@Body() formData: ApplicationFormData,
@CurrentUser() user: any,
) {
await this.runService.run(
ProcessApplicationWorkflow.name,
ApplicationsWorkspace.name,
{
applicantData: formData,
submittedAt: new Date().toISOString(),
source: 'web-form',
},
user.id,
);
return {
message: 'Application submitted successfully',
status: 'processing',
};
}
}Example 4: Batch Processing
Process multiple items by triggering workflows in batch:
import { Injectable } from '@nestjs/common';
import { RunService } from '@loopstack/core';
import { ProcessOrderWorkflow } from './workflows/process-order.workflow';
import { OrdersWorkspace } from './orders-workspace';
@Injectable()
export class OrderProcessingService {
constructor(private readonly runService: RunService) {}
async processPendingOrders() {
const pendingOrders = await this.getPendingOrders();
// Process orders in parallel
const promises = pendingOrders.map(order =>
this.runService.run(
ProcessOrderWorkflow.name,
OrdersWorkspace.name,
{
orderId: order.id,
orderData: order,
priority: order.priority,
},
order.userId,
)
);
await Promise.all(promises);
return { processed: pendingOrders.length };
}
private async getPendingOrders() {
// Implementation to fetch pending orders
return [];
}
}