Skip to main content

Getting Started with @ductape/client

The @ductape/client package is the core JavaScript/TypeScript SDK for Ductape. It works with any framework or vanilla JavaScript and provides a simple, type-safe interface to all Ductape services.

Installation

npm install @ductape/client
# or
yarn add @ductape/client
# or
pnpm add @ductape/client

Basic Setup

Frontend: use only publishable key

In browser or any frontend code, never use accessKey. Use a publishable key and point the client at your Ductape proxy (BFF). The proxy resolves the key and enforces scope. Get your publishable key from Workbench → Tokens → Publishable Key.

Session required in every request (publishable key)

When using a publishable key, every request (e.g. databases.query, storage.upload, actions.run) must include a session property in the options object—a session token issued by your backend. Session methods (sessions.start, sessions.verify, etc.) are not available from the client with a publishable key; they throw. Sessions can only be started and managed on the backend. See Sessions & Authentication and the main docs on frontend access key strategies.

1. Initialize the Client

import { Ductape } from '@ductape/client';

const ductape = new Ductape({
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
});

2. Connect for Real-time Features

If you need real-time subscriptions or WebSocket features, establish a connection:

await ductape.connect();

console.log('Connected to Ductape!');
tip

The connection is optional. You can use databases, storage, and other services over HTTP without calling connect(). Only call it if you need real-time subscriptions or presence features.

3. Use Services

const sessionToken = getSessionFromYourBackend(); // from your auth

// Database operations (include session in every request when using publishable key)
await ductape.databases.connect({ database: 'main', session: sessionToken });

const users = await ductape.databases.query({
table: 'users',
where: { active: true },
limit: 10,
session: sessionToken,
});

console.log('Active users:', users.rows);

Configuration (frontend)

In frontend code you only need publishableKey. Never use accessKey in the browser.

interface IDuctapeClientConfig {
publishableKey: string; // From Workbench → Tokens → Publishable Key
}

Example

const ductape = new Ductape({
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
});
tip

Get your publishable key from the Workbench (Tokens → Publishable Key). Configure scope there to control which module/methods the key can call.

Server-side only

accessKey is for server-side or Node only (e.g. BFF, scripts). Do not use it in frontend or browser code.

Quick Examples

Database Query

const sessionToken = getSessionFromYourBackend();

const result = await ductape.databases.query({
table: 'products',
where: {
category: 'electronics',
price: { $lt: 1000 }
},
orderBy: [{ column: 'price', order: 'asc' }],
limit: 20,
session: sessionToken,
});

console.log(`Found ${result.count} products`);

Insert Data

const sessionToken = getSessionFromYourBackend();

const newUser = await ductape.databases.insert({
table: 'users',
data: {
name: 'Alice Johnson',
email: 'alice@example.com',
role: 'user'
},
session: sessionToken,
});

console.log('Created user:', newUser.rows[0]);

Upload a File

const sessionToken = getSessionFromYourBackend();
const file = document.getElementById('fileInput').files[0];

const result = await ductape.storage.upload({
file: file,
path: 'uploads/documents',
session: sessionToken,
onProgress: (progress) => {
console.log(`Upload progress: ${progress.percentage}%`);
}
});

console.log('File uploaded:', result.url);

Execute a Workflow

const sessionToken = getSessionFromYourBackend();

const execution = await ductape.workflows.execute({
workflow: 'process-order',
input: {
orderId: '12345',
userId: 'user-789'
},
session: sessionToken,
});

console.log('Workflow execution:', execution.executionId);

Subscribe to Database Changes

const sessionToken = getSessionFromYourBackend();
await ductape.connect(); // WebSocket connection required

const subscription = ductape.databases.subscribe({
table: 'messages',
where: { channel: 'general' },
session: sessionToken,
onChange: (event) => {
console.log('Change detected:', event.type, event.data);

if (event.type === 'insert') {
console.log('New message:', event.data.new);
}
}
});

// Later: unsubscribe
subscription.unsubscribe();

Connection State Management

Monitor the connection state for real-time features:

await ductape.connect();

ductape.onConnectionStateChange((state) => {
console.log('Connection state:', state);
// States: 'connected', 'disconnected', 'connecting', 'reconnecting'
});

Error Handling

All async operations throw errors that you should handle:

const sessionToken = getSessionFromYourBackend();

try {
const result = await ductape.databases.query({
table: 'users',
where: { id: userId },
session: sessionToken,
});
console.log('User:', result.rows[0]);
} catch (error) {
console.error('Query failed:', error.message);

// Error types you might encounter:
// - Network errors
// - Authentication errors
// - Validation errors
// - Not found errors
}

TypeScript Support

The client is fully typed. You can provide type parameters for better intellisense:

interface User {
id: string;
name: string;
email: string;
createdAt: Date;
}

const sessionToken = getSessionFromYourBackend();

const result = await ductape.databases.query<User>({
table: 'users',
limit: 10,
session: sessionToken,
});

// result.rows is typed as User[]
const firstUser: User = result.rows[0];

Using with Different Frameworks

Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
<script type="module">
import { Ductape } from 'https://cdn.jsdelivr.net/npm/@ductape/client/+esm';

const ductape = new Ductape({
publishableKey: 'your-publishable-key',
});

async function loadUsers() {
const sessionToken = getSessionFromYourBackend(); // from your auth
const result = await ductape.databases.query({
table: 'users',
limit: 10,
session: sessionToken,
});

document.getElementById('users').innerHTML = result.rows
.map(u => `<li>${u.name}</li>`)
.join('');
}

loadUsers();
</script>
</head>
<body>
<ul id="users"></ul>
</body>
</html>

With React (without hooks)

import { Ductape } from '@ductape/client';
import { useEffect, useState } from 'react';

const ductape = new Ductape({
publishableKey: process.env.REACT_APP_PUBLISHABLE_KEY,
});

function UsersList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
async function loadUsers() {
try {
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'users',
limit: 10,
session: sessionToken,
});
setUsers(result.rows);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}

loadUsers();
}, []);

if (loading) return <div>Loading...</div>;

return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
tip

For React applications, we recommend using @ductape/react which provides hooks and better integration.

With Vue (without composables)

<script>
import { Ductape } from '@ductape/client';

const ductape = new Ductape({
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
});

export default {
data() {
return {
users: [],
loading: true
};
},
async mounted() {
try {
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'users',
limit: 10,
session: sessionToken,
});
this.users = result.rows;
} catch (error) {
console.error(error);
} finally {
this.loading = false;
}
}
};
</script>

<template>
<div v-if="loading">Loading...</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
tip

For Vue 3 applications, we recommend using @ductape/vue which provides composables and better integration.

Next Steps

Helper Functions

The package also exports a createClient helper:

import { createClient } from '@ductape/client';

const ductape = createClient({
publishableKey: import.meta.env.VITE_PUBLISHABLE_KEY,
});

// Equivalent to: new Ductape({ ... })