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
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.
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!');
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,
});
Get your publishable key from the Workbench (Tokens → Publishable Key). Configure scope there to control which module/methods the key can call.
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>
);
}
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>
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({ ... })