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:Copy
npm install @databite/build zod
Step 3: Define Configuration Schemas
Createschemas.ts with the configuration schemas:
Copy
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
Createactions/send-message.ts for sending Slack messages:
Copy
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
Createsyncs/fetch-messages.ts for syncing channel messages:
Copy
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
Createindex.ts and put it all together:
Copy
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:Copy
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:Copy
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
