Skip to main content

Overview

This guide will walk you through creating your first connector using Databite. We’ll build a Slack connector that can send messages and sync channel data, demonstrating the core concepts and patterns.

Prerequisites

Before starting, make sure you have:
  • Node.js >= 16.0.0 installed
  • Basic knowledge of TypeScript
  • A Slack app (we’ll create one in the steps below)

Step 1: Create a Slack App

  • First, create a Slack app at api.slack.com/apps
  • Add these scopes: chat:write, channels:read, users:read
  • Copy your Client ID, Client Secret, and note your Redirect URLs.

Step 2: Installation

Install the required packages:
npm install @databite/build zod

Step 3: Define Configuration Schemas

Create schemas.ts with the configuration schemas:
import { z } from "zod";

export const SlackIntegrationConfigSchema = z.object({
  clientId: z.string(),
  clientSecret: z.string(),
  redirectUri: z.string(),
  scopes: z.array(z.string()),
});

export const SlackConnectionConfigSchema = z.object({
  workspace: z.string(),
  userId: z.string(),
  accessToken: z.string(),
  teamId: z.string(),
  teamName: z.string(),
});

Step 4: Create Actions

Create actions/send-message.ts for sending Slack messages:
import { z } from "zod";
import { createAction } from "@databite/build";
import { SlackConnectionConfigSchema } from "../schemas";

export const SendMessageInputSchema = z.object({
  channelId: z.string().describe("Slack channel ID (e.g., #general or C1234567890)"),
  text: z.string().describe("Message text to send"),
});

export const SendMessageOutputSchema = z.object({
  ok: z.boolean(),
  channel: z.string(),
  ts: z.string(),
  message: z.object({
    text: z.string(),
    user: z.string().optional(),
  }),
});

export const sendMessage = createAction<
  typeof SendMessageInputSchema,
  typeof SendMessageOutputSchema,
  typeof SlackConnectionConfigSchema
>({
  label: "Send Slack Message",
  description: "Send a message to a Slack channel",
  inputSchema: SendMessageInputSchema,
  outputSchema: SendMessageOutputSchema,
  maxRetries: 3,
  timeout: 30000,

  handler: async (params, connection) => {
    const { channelId, text } = params;
    const { accessToken } = connection.config;

    const response = await fetch("https://slack.com/api/chat.postMessage", {
      method: "POST",
      headers: {
        "Content-Type": "application/json; charset=utf-8",
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify({
        channel: channelId,
        text,
      }),
    });

    const data = await response.json();

    if (!data.ok) {
      throw new Error(`Slack API error: ${data.error}`);
    }

    return data;
  },
});

Step 5: Create Syncs

Create syncs/fetch-messages.ts for syncing channel messages:
import { z } from "zod";
import { createSync } from "@databite/build";
import { SlackConnectionConfigSchema } from "../schemas";

export const FetchMessagesOutputSchema = z.array(
  z.object({
    ts: z.string(),
    user: z.string(),
    text: z.string(),
    channel: z.string(),
    timestamp: z.string(),
  })
);

export const fetchMessages = createSync<
  typeof FetchMessagesOutputSchema,
  typeof SlackConnectionConfigSchema
>({
  label: "Fetch Slack Messages",
  description: "Fetch recent messages from Slack channels",
  syncInterval: 360, // Every 6 hours (360 minutes)
  outputSchema: FetchMessagesOutputSchema,
  maxRetries: 3,
  timeout: 60000,

  handler: async (connection) => {
    const { accessToken } = connection.config;

    // First get list of channels
    const channelsResponse = await fetch(
      "https://slack.com/api/conversations.list",
      {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    );

    const channelsData = await channelsResponse.json();

    if (!channelsData.ok) {
      throw new Error(`Failed to fetch channels: ${channelsData.error}`);
    }

    const messages: z.infer<typeof FetchMessagesOutputSchema> = [];

    // Fetch messages from each channel (limit to first 5 channels for demo)
    for (const channel of channelsData.channels.slice(0, 5)) {
      try {
        const messagesResponse = await fetch(
          `https://slack.com/api/conversations.history?channel=${channel.id}&limit=10`,
          {
            headers: {
              Authorization: `Bearer ${accessToken}`,
            },
          }
        );

        const messagesData = await messagesResponse.json();

        if (messagesData.ok) {
          for (const message of messagesData.messages) {
            messages.push({
              ts: message.ts,
              user: message.user || "",
              text: message.text || "",
              channel: channel.name,
              timestamp: new Date(parseFloat(message.ts) * 1000).toISOString(),
            });
          }
        }
      } catch (error) {
        console.warn(`Failed to fetch messages for channel ${channel.name}:`, error);
      }
    }

    return messages;
  },
});

Step 6: Build the Connector

Create index.ts and put it all together:
import { createConnector } from "@databite/build";
import { z } from "zod";
import { SlackIntegrationConfigSchema, SlackConnectionConfigSchema } from "./schemas";
import { sendMessage } from "./actions/send-message";
import { fetchMessages } from "./syncs/fetch-messages";

export const slack = createConnector<
  typeof SlackIntegrationConfigSchema,
  typeof SlackConnectionConfigSchema
>()
  .withIdentity("slack", "Slack")
  .withVersion("1.0.0")
  .withAuthor("Your Name")
  .withLogo("https://slack.com/img/icons/app-57.png")
  .withDocumentationUrl("https://api.slack.com")
  .withDescription("Slack connector for messaging and team communication")
  .withIntegrationConfig(SlackIntegrationConfigSchema)
  .withConnectionConfig(SlackConnectionConfigSchema)
  .withCategories("communication")
  .withTags("messaging", "team", "communication")
  .withAuthenticationFlow("slackAuth", (flow) =>
    flow
      // Step 1: OAuth authentication
      .oauth<"authenticate", { code: string }>("authenticate", {
        authUrl: (input) => {
          const state = Math.random().toString(36).substring(7);

          const params = new URLSearchParams({
            client_id: input.integration.clientId,
            scope: input.integration.scopes.join(","),
            redirect_uri: input.integration.redirectUri,
            state: state,
          });

          return `https://slack.com/oauth/v2/authorize?${params.toString()}`;
        },
        title: "Authorize Slack",
        description: "Click below to open Slack and authorize this application",
        buttonLabel: "Connect to Slack",
        popupWidth: 600,
        popupHeight: 800,
        timeout: 300000, // 5 minutes
        extractParams: (url) => ({
          code: url.searchParams.get("code") || "",
        }),
      })

      // Step 2: Exchange code for access token
      .http("tokenExchange", {
        url: "https://slack.com/api/oauth.v2.access",
        method: "POST",
        headers: () => ({
          "Content-Type": "application/x-www-form-urlencoded",
        }),
        body: (ctx) =>
          new URLSearchParams({
            client_id: ctx.integration.clientId,
            client_secret: ctx.integration.clientSecret,
            code: ctx.authenticate.code,
            redirect_uri: ctx.integration.redirectUri,
          }).toString(),
        returnType: {} as any,
      })

      // Step 3: Transform the response into our desired format
      .transform<"result", z.infer<typeof SlackConnectionConfigSchema>>(
        "result",
        (ctx) => {
          const { tokenExchange } = ctx;

          if (!tokenExchange.ok) {
            throw new Error("Failed to authenticate with Slack");
          }

          return {
            workspace: tokenExchange.team.name,
            userId: tokenExchange.authed_user.id,
            accessToken: tokenExchange.access_token,
            teamId: tokenExchange.team.id,
            teamName: tokenExchange.team.name,
          };
        }
      )

      // Step 4: Show success message
      .display("success", {
        title: "Successfully Connected!",
        content: (ctx) =>
          `You've successfully connected to ${ctx.result.teamName}. You can now send messages and sync data from your Slack workspace.`,
        continueLabel: "Done",
      })
      .returns((ctx) => ctx.result)
  )
  .withRefresh(async (connection, _integration) => {
    // Slack tokens don't expire, so just return current config
    return connection.config;
  })
  .withRateLimit({
    requests: 100, // 100 requests per minute
    windowMs: 60000, // 1 minute
    strategy: "per-connection", // rate limit per connection
  })
  .withActions({ "Send Message": sendMessage })
  .withSyncs({ "Fetch Messages": fetchMessages })
  .build();

Step 7: Test Your Connector

Create a test file to verify your connector works:
import { slack } from "./index";

// Test integration creation
const integration = slack.createIntegration("Test Slack Integration", {
  clientId: process.env.SLACK_CLIENT_ID!,
  clientSecret: process.env.SLACK_CLIENT_SECRET!,
  redirectUri: "http://localhost:3001/auth/slack/callback",
  scopes: ["chat:write", "channels:read"],
});

console.log("Integration created:", integration);

// Test the connector metadata
console.log("Connector info:", {
  id: slack.id,
  name: slack.name,
  actions: Object.keys(slack.actions),
  syncs: Object.keys(slack.syncs),
});

Step 8: Use in a Server

Add your connector to a Databite server:
import { DatabiteServer } from "@databite/server";
import { slack } from "./slack-connector";

const server = new DatabiteServer({
  port: 3001,
  engineConfig: {
    connectors: [slack],
  },
});

server.addIntegration(slack.createIntegration("Slack Integration", {
      clientId: process.env.SLACK_CLIENT_ID!,
      clientSecret: process.env.SLACK_CLIENT_SECRET!,
      redirectUri: process.env.SLACK_REDIRECT_URI!,
      scopes: [
        "chat:write",
        "channels:read",
        "users:read",
        "team:read",
        "channels:history",
      ],
    })
);

Next Steps

  • Add more actions (create channels, invite users, etc.)
  • Add more syncs (users, files, etc.)
  • Contribute your connector to our repo for others to use