PlatformsNode.js
Node.js + Express
Complete @aelio/sdk integration with Express.js — the recommended starting point.
This guide walks through a full Express + Aelio integration, based on the official examples/nodejs-express example and the Shopping Testbed demo.
1. Create project
mkdir my-saas && cd my-saas
npm init -y
npm install @aelio/sdk express2. Register tools
Create src/index.ts:
import { aelio } from '@aelio/sdk';
import express from 'express';
const app = express();
app.use(express.json());
// Declare lifecycle state
aelio.state('active', {
description: 'Standard customer with full access to order and subscription tools.',
});
// Declare a soft policy
aelio.policy('concise', {
description: 'Keep replies short and actionable.',
severity: 'soft',
});
// Read tool — auto-allowed
aelio.expose('getOrderStatus', async ({ orderId }, ctx) => {
return {
orderId,
customerId: ctx.customerId,
status: 'shipped',
tracking: '1Z999AA10123456784',
};
}, {
description: 'Get the status of a customer order',
params: { orderId: 'string' },
safety: 'read',
});
// Write tool — requires user confirmation
aelio.expose('cancelOrder', async ({ orderId }, ctx) => {
return {
orderId,
customerId: ctx.customerId,
status: 'cancelled',
cancelledAt: new Date().toISOString(),
};
}, {
description: 'Cancel a pending customer order',
params: { orderId: 'string' },
safety: 'write',
});
// Tool with optional parameters
aelio.expose('listOrders', async ({ status, limit }, ctx) => {
const all = [
{ orderId: 'A123', status: 'shipped' },
{ orderId: 'B456', status: 'pending' },
];
const filtered = status ? all.filter(o => o.status === status) : all;
return {
customerId: ctx.customerId,
orders: filtered.slice(0, limit ?? filtered.length),
};
}, {
description: "List the customer's orders, optionally filtered by status",
params: {
status: { type: 'string', enum: ['shipped', 'pending'], optional: true },
limit: 'number?',
},
safety: 'read',
});3. Connect to Aelio server
await aelio.listen({
secret: process.env.AELIO_SDK_SECRET ?? 'change-me-in-production',
url: process.env.AELIO_SERVER_URL ?? 'ws://127.0.0.1:3010',
});4. Start Express
app.get('/health', (_req, res) => {
res.json({ ok: true });
});
const port = Number(process.env.PORT ?? 8080);
app.listen(port, () => {
console.log(`Backend listening on :${port} with Aelio SDK connected`);
});5. Run
AELIO_SDK_SECRET=change-me-in-production \
AELIO_SERVER_URL=ws://127.0.0.1:3010 \
npx tsx src/index.ts6. Embed chat widget
Add to your frontend HTML:
<script
src="http://localhost:3010/widget.js"
data-server-url="http://localhost:3010"
data-customer-id="user_abc123"
data-email="user@example.com"
></script>Advanced patterns
Tool registration wrapper (logging)
function exposeTool(name: string, handler: Function, schema: object) {
aelio.expose(name, async (args: any, ctx: any) => {
const start = Date.now();
console.log(`[aelio] ${name} start`, { customerId: ctx.customerId });
try {
const result = await handler(args, ctx);
console.log(`[aelio] ${name} ok (${Date.now() - start}ms)`);
return result;
} catch (err: any) {
console.error(`[aelio] ${name} error:`, err.message);
throw err;
}
}, schema);
}Lifecycle state sync
Push state when business events happen:
app.post('/api/orders', async (req, res) => {
const order = await createOrder(req.body);
await aelio.setCustomerState(req.user.id, 'post_purchase', 'Order placed');
res.json(order);
});Chat config endpoint (no secrets)
app.get('/api/chat-config', (_req, res) => {
res.json({
serverUrl: process.env.AELIO_HTTP_URL ?? 'http://localhost:3010',
});
});Magic link for widget auth
app.post('/api/chat-token', async (req, res) => {
const response = await fetch(`${AELIO_HTTP_URL}/auth/magic-link`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.AELIO_SDK_SECRET}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: req.user.email, externalId: req.user.id }),
});
const data = await response.json();
res.json({ magicLinkUrl: data.url });
});BYO WhatsApp channel
aelio.onSend(async ({ channel, to, content }) => {
await twilioClient.messages.create({ to, body: content });
});
app.post('/whatsapp-webhook', (req, res) => {
const { from, text } = parseTwilioMessage(req.body);
aelio.ingest({ channel: 'whatsapp', from, text });
res.sendStatus(200);
});Parameter declaration reference
| Form | Example | Meaning |
|---|---|---|
| Shorthand | 'string' | Required |
| Optional | 'string?' | Optional (trailing ?) |
| Object | type: 'string', optional: true, enum: [...] | Full control |
Supported types: string, number, integer, boolean, array, object.
Next steps
- Vanilla Node.js demo — full e-commerce Shopping Testbed
- Lifecycle states — state gating in the Express guide
- Widget authentication — production auth flow