@bondi-labs/integration-sdk - v0.0.1
    Preparing search index...

    Getting started — NestJS

    This walkthrough builds a NestJS app that:

    1. Emits a typed contact.created trigger when a new contact is saved.
    2. Exposes a createContact action that Bondi can call from a workflow, verified by HMAC.

    End-to-end time: ~10 minutes.

    • Node.js ≥ 18
    • A Bondi workspace (sign up)
    • An existing NestJS app (or nest new my-crm to scaffold one)
    npm install @bondi-labs/integration-sdk reflect-metadata zod
    
    npx bondi init
    

    This opens your browser, prompts you to approve the CLI, asks for the integration name (e.g. "My CRM"), and writes the result to .env:

    BONDI_API_URL=https://automation.heybondi.com
    BONDI_WORKSPACE_ID=ws-...
    BONDI_INTEGRATION_TOKEN=bnd_tok_...

    Save the token now. It's shown once; rotation is possible later but the previous token is only valid for 24h after rotation.

    Triggers live as injectable classes that extend BondiTriggerBase<TZodSchema>. The Zod schema gives you full type safety on emit().

    // src/bondi/triggers/contact-created.trigger.ts
    import { BondiTrigger, BondiTriggerBase } from "@bondi-labs/integration-sdk/nestjs";
    import { z } from "zod";

    export const contactCreatedPayload = z.object({
    id: z.string(),
    email: z.string().email(),
    fullName: z.string(),
    });

    @BondiTrigger({
    name: "contact.created",
    label: "Contact Created",
    payload: contactCreatedPayload,
    })
    export class ContactCreatedTrigger extends BondiTriggerBase<typeof contactCreatedPayload> {}
    // src/bondi/bondi.module.ts
    import { Module } from "@nestjs/common";
    import { BondiModule } from "@bondi-labs/integration-sdk/nestjs";
    import { ContactCreatedTrigger } from "./triggers/contact-created.trigger";

    @Module({
    imports: [
    BondiModule.forRoot({
    integration: { name: "My CRM", slug: "my-crm", category: "crm" },
    triggers: [ContactCreatedTrigger],
    }),
    ],
    exports: [BondiModule],
    })
    export class AppBondiModule {}

    Import AppBondiModule from AppModule. The module is global, so any service can inject any registered trigger.

    // src/contacts/contacts.service.ts
    import { Injectable } from "@nestjs/common";
    import { ContactCreatedTrigger } from "../bondi/triggers/contact-created.trigger";

    @Injectable()
    export class ContactsService {
    constructor(private contactCreated: ContactCreatedTrigger) {}

    async create(dto: { email: string; fullName: string }) {
    const contact = await saveToDb(dto);
    this.contactCreated.emit({
    id: contact.id,
    email: contact.email,
    fullName: contact.fullName,
    });
    // ^ Type-checked against contactCreatedPayload's Zod schema.
    return contact;
    }
    }

    emit() is fire-and-forget — failures don't bubble into your business logic. Use emitAndWait() if you need to confirm Bondi accepted the event before returning.

    // src/contacts/contacts.controller.ts
    import { Body, Controller, Post, UseGuards } from "@nestjs/common";
    import {
    BondiAction,
    BondiGuard,
    BondiService,
    } from "@bondi-labs/integration-sdk/nestjs";
    import { z } from "zod";
    import { ContactsService } from "./contacts.service";

    const createContactBody = z.object({
    email: z.string().email(),
    fullName: z.string(),
    });

    @BondiService({ name: "contacts", label: "Contacts" })
    @Controller("contacts")
    export class ContactsController {
    constructor(private contacts: ContactsService) {}

    @UseGuards(BondiGuard)
    @BondiAction({
    name: "createContact",
    label: "Create Contact",
    body: createContactBody,
    })
    @Post()
    async create(@Body() dto: z.infer<typeof createContactBody>) {
    return this.contacts.create(dto);
    }
    }

    BondiGuard verifies the HMAC signature against the raw bytes of the request body. NestJS must be configured to preserve them:

    // src/main.ts
    import { NestFactory } from "@nestjs/core";
    import { AppModule } from "./app.module";

    async function bootstrap() {
    const app = await NestFactory.create(AppModule, { rawBody: true });
    await app.listen(3000);
    }
    bootstrap();

    Without rawBody: true, all BondiGuard checks will fail with missing_headers.

    After defining triggers and actions, push the schema to Bondi so the workflow editor knows what fields exist:

    npx bondi sync
    

    By default this imports ./src/bondi.integration.ts — a file that default-exports the integration definition:

    // src/bondi.integration.ts
    import { defineIntegration } from "@bondi-labs/integration-sdk/core";
    import { contactCreatedPayload } from "./bondi/triggers/contact-created.trigger";

    export default defineIntegration({
    name: "My CRM",
    slug: "my-crm",
    category: "crm",
    baseUrl: "https://api.mycrm.com/v1",
    services: [
    {
    name: "contacts",
    label: "Contacts",
    triggers: [
    { name: "contact.created", label: "Contact Created", payload: contactCreatedPayload },
    ],
    actions: [
    // ... define each action with its Zod schemas
    ],
    },
    ],
    });

    Use --entry to point to a different file, --dry-run to preview, or --fail-on-error for CI.