Integration
@xond/workflow integrates seamlessly with @xond/api generated services and @xond/ui components.
Backend Integration
Workflow API Endpoints
Your backend should provide these endpoints:
Create Workflow Instance
POST /api/workflow-instances
{
workflowId: string,
contextEntity: string,
contextEntityId: string,
values?: any,
}
Response:
{
workflowInstanceId: string,
workflowId: string,
currentNodeId: string,
values: {},
// ... other instance properties
}
Execute Action
POST /api/workflow-instances/action
{
instanceId: string,
handle: string,
value: any,
}
Response:
{
success: boolean,
instanceId: string,
currentNodeId: string,
completed: boolean,
}
Walk Workflow
POST /api/workflow-instances/walk
{
instanceId: string,
}
Response:
{
success: boolean,
currentNodeId: string,
completed: boolean,
}
Using Generated Services
If you have generated services from @xond/api:
import { serviceRegistry } from "./modules/json/services/generated/serviceRegistry";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// Create workflow instance
app.post("/api/workflow-instances", async (req, res) => {
const instance = await serviceRegistry["workflowInstance"].createWorkflowInstance(prisma, {
workflowId: req.body.workflowId,
contextEntity: req.body.contextEntity,
contextEntityId: req.body.contextEntityId,
values: req.body.values || {},
});
res.json(instance);
});
// Execute action
app.post("/api/workflow-instances/action", async (req, res) => {
const result = await serviceRegistry["workflowInstance"].executeAction(prisma, {
instanceId: req.body.instanceId,
handle: req.body.handle,
value: req.body.value,
});
res.json(result);
});
Frontend Integration
Using with @xond/ui Components
Custom forms can use @xond/ui components:
import { TextInput, Button, Form, useRemoteDataSource } from "@xond/ui";
import { CustomFormProps } from "@xond/workflow";
export const ModelForm: React.FC<CustomFormProps> = ({
returnValue,
inputData,
}) => {
const dataSource = useRemoteDataSource({
resourceName: "person",
entityName: "Person",
endpoint: "/api/persons",
});
// Use Form component from @xond/ui
return (
<Form
formModel={personFormModel}
dataSource={dataSource}
autoSave={(status) => {
returnValue(status);
}}
/>
);
};
Using Generated Models
Use models generated by @xond/api:
import { modelRegistry, columnModelRegistry } from "@your-app/generated/models";
import { createFormModel, createColumnModel } from "@xond/ui";
import { CustomFormProps } from "@xond/workflow";
export const PersonForm: React.FC<CustomFormProps> = ({
returnValue,
inputData,
}) => {
const PersonModel = modelRegistry.person;
const fields = PersonModel.fields;
// Create form model
const formModel = createFormModel(fields);
// Use with Form component
return (
<Form
formModel={formModel}
dataSource={{ formData: inputData }}
autoSave={(status) => returnValue(status)}
/>
);
};
Complete Integration Example
Backend (Express + Prisma)
// routes/workflow-instances.ts
import express from "express";
import { PrismaClient } from "@prisma/client";
import { serviceRegistry } from "../modules/json/services/generated/serviceRegistry";
const router = express.Router();
const prisma = new PrismaClient();
// Create workflow instance
router.post("/", async (req, res) => {
try {
const instance = await serviceRegistry["workflowInstance"].createWorkflowInstance(prisma, {
workflowId: req.body.workflowId,
contextEntity: req.body.contextEntity,
contextEntityId: req.body.contextEntityId,
values: req.body.values || {},
});
res.json(instance);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Execute action
router.post("/action", async (req, res) => {
try {
const result = await serviceRegistry["workflowInstance"].executeAction(prisma, {
instanceId: req.body.instanceId,
handle: req.body.handle,
value: req.body.value,
});
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get workflow instance
router.get("/:id", async (req, res) => {
try {
const instance = await serviceRegistry["workflowInstance"].getWorkflowInstanceById(prisma, {
id: req.params.id,
include: ["node", "workflow"],
});
res.json(instance);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;
Frontend (React)
// pages/WorkflowExecution.tsx
import { WorkflowUI, WorkflowActions, useWorkflows } from "@xond/workflow";
import { useState } from "react";
import { useParams } from "react-router-dom";
import { jsonApi } from "@xond/ui";
function WorkflowExecutionPage() {
const { instanceId } = useParams<{ instanceId: string }>();
const {
instance,
node,
values,
uiStatus,
setUIStatus,
} = useWorkflows({
entity: "workflowInstance",
id: instanceId || "",
actionEndpoint: "/api/workflow-instances/action",
walkEndpoint: "/api/workflow-instances/walk",
includes: ["node", "workflow"],
});
const [updatedData, setUpdatedData] = useState<any>(null);
const handleReturnValue = (value: { ready: boolean; data: any }) => {
setUpdatedData(value);
setUIStatus(value);
};
const handleAction = async ({ value, handle }: { value: any; handle: string }) => {
try {
await jsonApi.post("workflowInstance", "action", {
instanceId,
handle,
value,
});
window.location.reload();
} catch (error) {
console.error("Error executing action:", error);
alert("Failed to proceed");
}
};
if (!instance || !node) {
return <div>Loading...</div>;
}
return (
<div className="container mx-auto p-4">
<WorkflowUI
currentNode={node}
workflowInstance={instance}
returnValue={handleReturnValue}
/>
<WorkflowActions
currentNode={node}
workflowInstance={instance}
oldValue={values}
updatedDataFromUI={updatedData}
callBack={handleAction}
/>
</div>
);
}
Custom Form with Integration
// components/forms/PersonForm.tsx
import { Form, createFormModel, useRemoteDataSource } from "@xond/ui";
import { CustomFormProps } from "@xond/workflow";
import { modelRegistry } from "@your-app/generated/models";
export const PersonForm: React.FC<CustomFormProps> = ({
returnValue,
inputData,
}) => {
const PersonModel = modelRegistry.person;
const formModel = createFormModel(PersonModel.fields);
const dataSource = useRemoteDataSource({
resourceName: "person",
entityName: "Person",
endpoint: "/api/persons",
});
// Set initial data
useEffect(() => {
if (inputData) {
dataSource.startEdit(inputData);
}
}, [inputData]);
return (
<Form
formModel={formModel}
dataSource={dataSource}
autoSave={(status) => {
returnValue(status);
}}
/>
);
};
Register Custom Form
// App.tsx
import { FormRegistryContext } from "@xond/workflow";
import { PersonForm } from "./components/forms/PersonForm";
function App() {
const customForms = {
PersonForm: PersonForm,
};
return (
<FormRegistryContext customForms={customForms}>
{/* Your app */}
</FormRegistryContext>
);
}
Workflow Editor Integration
Saving Workflows
The workflow editor saves workflows to the database:
// Backend endpoint
POST /api/workflows
{
code: string,
name: string,
description: string,
nodes: Node[],
edges: Edge[],
}
Loading Workflows
Load workflows for editing:
// Backend endpoint
GET /api/workflows/:id
Response:
{
workflowId: string,
code: string,
name: string,
description: string,
nodes: Node[],
edges: Edge[],
}
Best Practices
- Use generated services – Leverage
@xond/apigenerated services - Handle errors – Implement proper error handling in API calls
- Validate data – Validate workflow instance data on backend
- Use @xond/ui components – Leverage existing UI components in custom forms
- Register custom forms – Add all custom forms to FormRegistryContext
- Test workflows – Create test instances to verify integration
- Handle loading states – Show loading indicators during API calls
Next Steps
- Review workflow execution
- Learn about custom forms
- See node configuration