Aelio
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 express

2. 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.ts

6. 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',
  });
});
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

FormExampleMeaning
Shorthand'string'Required
Optional'string?'Optional (trailing ?)
Objecttype: 'string', optional: true, enum: [...]Full control

Supported types: string, number, integer, boolean, array, object.

Next steps

On this page