Compose SDK Documentation

website·Indexed 31 days ago

https://docs.composehq.com

An open-source platform and SDK for rapidly building internal apps. Supports TypeScript, JavaScript, and Python, with integration guides for Node.js, Koa, NestJS, and Django. Features include a library of UI components, page actions like page.add(), page.update(), and page.toast(), and tools for building multipage apps, forms, and file handling.

Tokens
38.8K
Snippets
196
Records
250
Agent score
47%

What's inside Compose

  1. Overview of the Compose platform

    Compose is an open-source platform designed for building internal support dashboards, ops tools, and admin panels using backend code. It consists of two primary components: the Compose SDK, which provides 40+ professional-grade UI components (tables, forms, charts) that connect directly to backend data and logic, and the Compose web dashboard, which renders the UIs and provides built-in audit logs and Role-Based Access Control (RBAC).
  2. Create a visual separator with the Divider component

    The ui.divider() component is used to visually separate content with a horizontal or vertical line, helping create logical groupings and improving UI scannability. It is commonly used within layout components like stacks or rows.
    ui.stack([
      ui.statistic("Active Users", 1250),
      ui.divider(),
      ui.statistic("Sign Ups", 320),
      ui.divider(),
      ui.statistic("Churned Users", 12)
    ])
  3. Use ui.stack to group and layout components

    The ui.stack component is a generic container (rendered as a div with flexbox) used to group other components. By default, it arranges its children in a vertical stack. It is primarily used for controlling the layout of children and applying styles to the app's structure.
    ui.stack(
        [
            ui.text("Choose an action"),
            ui.row([
                ui.button("view-btn", { label: "View" }),
                ui.button("edit-btn", { label: "Edit", appearance: "outline" }),
                ui.button("delete-btn", { label: "Delete", appearance: "danger" })
            ])
        ],
        { align: "center" }
    )
  4. Use the Row component to group elements horizontally

    The ui.row component is a generic container used to group other components. It renders as a div with a flexbox layout, arranging its children in a horizontal row by default. It serves as a convenience wrapper around ui.stack with the direction property preset to "horizontal". Consequently, all properties available to ui.stack are also available to ui.row.
    ui.row([
        ui.button("view-btn", { label: "View" }),
        ui.button("edit-btn", { label: "Edit", appearance: "outline" }),
        ui.button("delete-btn", { label: "Delete", appearance: "danger" })
    ])
  5. Use the ui.form component to group inputs

    The ui.form component is a generic container used to group other input components. It inherits all functionality from the ui.stack component while providing additional utilities for form validation, submission, and state management.
  6. Understand the Compose hybrid architecture

    Compose uses a hybrid architecture where business logic and secrets remain within the user's infrastructure, while UI rendering and network communication are managed by Compose. The Compose SDK establishes a secure, encrypted background websocket connection to Compose servers, which act as a proxy between the SDK and the browser. This ensures that the SDK never connects directly to the browser, allowing Compose to authenticate both endpoints. Method calls (e.g., page.add(), page.toast()) and user interactions are serialized into JSON for two-way communication.
  7. Implement a global navigation pane in Compose apps

    Use Compose.Navigation to create a consistent navigation sidebar across multiple pages. This is best for linking between top-level pages. The navigation pane is instantiated once and then assigned to the navigation property of each Compose.App that should display it.
    import { Compose } from "@composehq/sdk";
    
    // Create a navigation pane with a list of routes and an optional logo
    const nav = new Compose.Navigation(
        ["home-page", "settings"],
        {
            logoUrl: "https://composehq.com/dark-logo-with-text.svg",
        }
    );
    
    const homePage = new Compose.App({
        route: "home-page",
        handler: () => page.add(() => ui.header("Home Page")),
        navigation: nav, // show nav pane on home page
    });
    
    const settings = new Compose.App({
        route: "settings",
        handler: () => page.add(() => ui.header("Settings Page")),
        navigation: nav, // show nav pane on settings page
    });
  8. Manage feedback and loading states

    Use page.toast() for notifications and page.loading() to display a loading spinner. page.loading() can optionally disable user interaction while the process is running.
    async function refundBilling(userId) {
        page.loading(true, { text: "Processing refund...", disableInteraction: true })
    
        try {
            await stripe.refundUser(userId)
            page.toast("Billing refunded successfully!", { appearance: "success" })
        } catch (error) {
            page.toast(error.message, { appearance: "error", title: "Error refunding billing" })
        } finally {
            page.loading(false)
        }
    }
  9. Create a navigation pane for multi-page apps

    The Compose.Navigation component renders a navigation pane that links to other apps within a project. This is used to facilitate navigation in multi-page applications by associating a shared navigation instance with multiple Compose.App instances.
    import { Compose } from "@composehq/sdk";
    
    // 1. Create a navigation pane with a list of app routes
    const nav = new Compose.Navigation(
        ["home-page", "settings"],
        {
            logoUrl: "https://composehq.com/dark-logo-with-text.svg",
        }
    );
    
    // 2. Assign the navigation pane to apps
    const homePage = new Compose.App({
        route: "home-page",
        handler: () => page.add(() => ui.header("Home Page")),
        navigation: nav,
    });
    
    const settings = new Compose.App({
        route: "settings",
        handler: () => page.add(() => ui.header("Settings Page")),
        navigation: nav,
    });
  10. Validate form data using the validate hook

    Forms support a validate hook to implement custom business logic. If the validate function returns a string, that string is displayed as an error to the user. Returning undefined (or nothing) indicates the form is valid. Additionally, some components like ui.emailInput and ui.urlInput have built-in format validation.
    page.add(() => 
        ui.form(
            "create-user-form", 
            [
                ui.radioGroup("role", ["Admin", "User", "Guest"]),
                ui.numberInput("age"),
            ],
            {
                validate: (form) => {
                    if (form.role === "Admin" && form.age < 18) {
                        return "Admins must be at least 18 years old"
                    }
                    return; 
                },
                onSubmit: (form) => createUser(form)
            }
        )
    )
    
  11. Integrate Compose SDK into a NestJS application

    To integrate Compose, create a dedicated module and service to manage the Compose client and app definitions. This ensures the client starts during the NestJS lifecycle and allows for easy dependency injection of other services into your internal apps.
    // 1. src/compose/compose.service.ts
    import { Injectable, OnModuleInit } from '@nestjs/common';
    import { Compose } from '@composehq/sdk';
    
    @Injectable()
    export class ComposeService implements OnModuleInit {
      onModuleInit() {
        const nav = new Compose.Navigation(['view-users', 'create-user']);
    
        const viewUsersApp = new Compose.App({
          route: 'view-users',
          navigation: nav,
          handler: ({ page, ui }) => {
            page.add(() => ui.header('View Users', { size: 'lg' }));
            const users = []; // Replace with real DB call
            page.add(() => ui.table('users-table', users));
          },
        });
    
        const createUserApp = new Compose.App({
          route: 'create-user',
          navigation: nav,
          handler: ({ page, ui }) => {
            page.add(() => ui.header('Create User', { size: 'lg' }));
            page.add(() =>
              ui.form(
                'create-user-form',
                [ui.textInput('name'), ui.emailInput('email')],
                {
                  onSubmit: (form) => {
                    // Handle form submission
                    page.toast('User created successfully', { appearance: 'success' });
                    page.link('view-users');
                  },
                },
              ),
            );
          },
        });
    
        const client = new Compose.Client({
          apiKey: 'YOUR_API_KEY',
          apps: [viewUsersApp, createUserApp],
        });
    
        client.connect();
      }
    }
    
    // 2. src/compose/compose.module.ts
    import { Module } from '@nestjs/common';
    import { ComposeService } from './compose.service';
    
    @Module({
      providers: [ComposeService],
      exports: [ComposeService],
    })
    export class ComposeModule {}
    
    // 3. src/app.module.ts
    import { Module } from '@nestjs/common';
    import { ComposeModule } from './compose/compose.module';
    
    @Module({
      imports: [ComposeModule],
      // ... other controllers and providers
    })
    export class AppModule {}
  12. Initialize and connect the Compose Client in Express

    To activate the internal apps, import the composeClient into your main application entry point (e.g., index.ts) and call the .connect() method, typically within the server's listen callback.
    import { composeClient } from "./compose";
    
    // ...
    
    app.listen(3000, () => {
        console.log("Server is running on port 3000");
        composeClient.connect();
    });