Database Operations
- @ductape/client
- React
- Vue 3
The @ductape/client provides a comprehensive API for working with databases including querying, inserting, updating, deleting, and subscribing to real-time changes.
Connecting to a Database
Before performing database operations, connect to your database. When using a publishable key, include session in every request:
const sessionToken = getSessionFromYourBackend();
await ductape.databases.connect({
database: 'main', // Your database name
session: sessionToken,
});
Querying Data
Basic Query
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'users',
limit: 10,
session: sessionToken,
});
console.log('Users:', result.rows);
console.log('Total count:', result.count);
Query with Filters
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'products',
where: {
category: 'electronics',
price: { $lt: 1000 },
inStock: true
},
limit: 20,
session: sessionToken,
});
Query Operators
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'orders',
session: sessionToken,
where: {
// Greater than
total: { $gt: 100 },
// Less than or equal
discount: { $lte: 20 },
// Not equal
status: { $ne: 'cancelled' },
// In array
category: { $in: ['electronics', 'books'] },
// Not in array
region: { $nin: ['blocked'] },
// Contains (for strings)
name: { $contains: 'phone' },
// Starts with
email: { $startsWith: 'admin@' }
}
});
Sorting and Pagination
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'posts',
where: { published: true },
orderBy: [
{ column: 'createdAt', order: 'desc' },
{ column: 'title', order: 'asc' }
],
limit: 20,
offset: 0,
session: sessionToken,
});
Including Related Data
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'posts',
include: [
{ relation: 'author' },
{ relation: 'comments', limit: 5 }
],
session: sessionToken,
});
// Access related data
result.rows.forEach(post => {
console.log(post.title);
console.log('Author:', post.author.name);
console.log('Comments:', post.comments);
});
Select Specific Fields
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.query({
table: 'users',
select: ['id', 'name', 'email'],
limit: 10,
session: sessionToken,
});
Inserting Data
Insert Single Record
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.insert({
table: 'users',
data: {
name: 'Alice Johnson',
email: 'alice@example.com',
role: 'user'
},
session: sessionToken,
});
console.log('Created user:', result.rows[0]);
Insert Multiple Records
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.insert({
table: 'products',
data: [
{ name: 'Product 1', price: 29.99 },
{ name: 'Product 2', price: 39.99 },
{ name: 'Product 3', price: 49.99 }
],
session: sessionToken,
});
console.log(`Inserted ${result.rows.length} products`);
Updating Data
Update with Filter
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.update({
table: 'users',
where: { id: 'user-123' },
data: {
name: 'Alice Smith',
updatedAt: new Date()
},
session: sessionToken,
});
console.log('Updated:', result.rows[0]);
Update Multiple Records
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.update({
table: 'orders',
where: {
status: 'pending',
createdAt: { $lt: new Date('2024-01-01') }
},
data: {
status: 'expired'
},
session: sessionToken,
});
console.log(`Updated ${result.count} orders`);
Deleting Data
Delete with Filter
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.delete({
table: 'sessions',
where: { userId: 'user-123' },
session: sessionToken,
});
console.log(`Deleted ${result.count} sessions`);
Delete Single Record
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.delete({
table: 'posts',
where: { id: 'post-456' },
session: sessionToken,
});
Upsert (Insert or Update)
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.upsert({
table: 'users',
where: { email: 'alice@example.com' },
data: {
name: 'Alice Johnson',
email: 'alice@example.com',
lastLogin: new Date()
},
session: sessionToken,
});
// If user exists, updates it; otherwise inserts new record
Aggregations
Count
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.aggregate({
table: 'orders',
operation: 'count',
where: { status: 'completed' },
session: sessionToken,
});
console.log('Completed orders:', result.value);
Sum
const sessionToken = getSessionFromYourBackend();
const result = await ductape.databases.aggregate({
table: 'orders',
operation: 'sum',
column: 'total',
where: { status: 'completed' },
session: sessionToken,
});
console.log('Total revenue:', result.value);
Average, Min, Max
const sessionToken = getSessionFromYourBackend();
// Average
const avg = await ductape.databases.aggregate({
table: 'products',
operation: 'avg',
column: 'price',
session: sessionToken,
});
// Minimum
const min = await ductape.databases.aggregate({
table: 'products',
operation: 'min',
column: 'price',
session: sessionToken,
});
// Maximum
const max = await ductape.databases.aggregate({
table: 'products',
operation: 'max',
column: 'price',
session: sessionToken,
});
Real-time Subscriptions
Subscribe to database changes in real-time:
// First, connect to WebSocket
await ductape.connect();
// Subscribe to table changes (include session when using publishable key)
const sessionToken = getSessionFromYourBackend();
const subscription = ductape.databases.subscribe({
table: 'messages',
where: { channel: 'general' },
session: sessionToken,
onChange: (event) => {
console.log('Change type:', event.type); // 'insert', 'update', 'delete'
if (event.type === 'insert') {
console.log('New record:', event.data.new);
} else if (event.type === 'update') {
console.log('Before:', event.data.old);
console.log('After:', event.data.new);
} else if (event.type === 'delete') {
console.log('Deleted:', event.data.old);
}
}
});
// Later: unsubscribe
subscription.unsubscribe();
Subscribe with Filters
const sessionToken = getSessionFromYourBackend();
const subscription = ductape.databases.subscribe({
table: 'notifications',
where: { userId: currentUserId, read: false },
session: sessionToken,
onChange: (event) => {
if (event.type === 'insert') {
showNotification(event.data.new);
}
}
});
Transactions
Execute multiple operations atomically:
const result = await ductape.databases.transaction(async (tx) => {
// Deduct from sender
await tx.update({
table: 'accounts',
where: { id: senderId },
data: { balance: { $decrement: amount } }
});
// Add to receiver
await tx.update({
table: 'accounts',
where: { id: receiverId },
data: { balance: { $increment: amount } }
});
// Record transaction
await tx.insert({
table: 'transactions',
data: {
from: senderId,
to: receiverId,
amount: amount,
timestamp: new Date()
}
});
return { success: true };
});
TypeScript Support
Use TypeScript for type-safe database operations:
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
}
// Query with types
const result = await ductape.databases.query<User>({
table: 'users',
where: { role: 'admin' }
});
// result.rows is typed as User[]
const firstUser: User = result.rows[0];
// Insert with types
const sessionToken = getSessionFromYourBackend();
const newUser = await ductape.databases.insert<User>({
table: 'users',
data: {
name: 'Bob',
email: 'bob@example.com',
role: 'user'
},
session: sessionToken,
});
Best Practices
- Always handle errors: Wrap database operations in try-catch blocks
- Use indexes: Ensure frequently queried fields are indexed in your database schema
- Limit results: Always use
limitto prevent fetching too much data - Use subscriptions wisely: Unsubscribe when components unmount to prevent memory leaks
- Batch operations: Use bulk inserts instead of multiple single inserts
- Optimize queries: Select only the fields you need with the
selectoption
Next Steps
React hooks for working with Ductape databases. These hooks provide automatic loading states, error handling, and React-specific optimizations.
When using a publishable key, include session in the options for every database call (query, insert, update, delete, subscription). Example: import { session } from './config' and pass session in the options object.
useDatabase
Connect to a database and get the connection state.
import { useDatabase } from '@ductape/react';
function MyComponent() {
const { isConnected, connect, disconnect, error } = useDatabase();
useEffect(() => {
connect({ database: 'main' });
}, []);
if (error) return <div>Error connecting: {error.message}</div>;
if (!isConnected) return <div>Connecting...</div>;
return <div>Connected to database</div>;
}
useDatabaseQuery
Query data from a database table with automatic loading and error states.
Basic Query
import { useDatabaseQuery } from '@ductape/react';
import { session } from './config';
function UsersList() {
const { data, isLoading, error, refetch } = useDatabaseQuery(
'users', // Query key
{
table: 'users',
limit: 10,
session,
}
);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<button onClick={() => refetch()}>Refresh</button>
<ul>
{(data?.data ?? []).map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
Query with Filters
import { session } from './config';
function ActiveUsers() {
const { data, isLoading } = useDatabaseQuery(
['users', 'active'],
{
table: 'users',
where: { active: true, role: 'user' },
orderBy: [{ column: 'createdAt', order: 'desc' }],
limit: 20,
session,
}
);
if (isLoading) return <div>Loading...</div>;
const rows = data?.data ?? [];
return (
<div>
<h2>Active Users ({rows.length})</h2>
<ul>
{rows.map((user: any) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
Reactive Query with Dependencies
import { session } from './config';
function ProductList() {
const [category, setCategory] = useState('electronics');
const [priceLimit, setPriceLimit] = useState(1000);
const { data, isLoading } = useDatabaseQuery(
['products', category, priceLimit],
{
table: 'products',
where: {
category: category,
price: { $lt: priceLimit }
},
orderBy: [{ column: 'price', order: 'asc' }],
session,
}
);
return (
<div>
<select value={category} onChange={(e) => setCategory(e.target.value)}>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
<option value="clothing">Clothing</option>
</select>
<input
type="number"
value={priceLimit}
onChange={(e) => setPriceLimit(Number(e.target.value))}
placeholder="Max price"
/>
{isLoading ? (
<div>Loading...</div>
) : (
<div>
{(data?.data ?? []).map((product: any) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
))}
</div>
)}
</div>
);
}
With TypeScript
import { session } from './config';
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
function TypedUsersList() {
const { data, isLoading } = useDatabaseQuery<User>(
'users',
{
table: 'users',
limit: 10,
session,
}
);
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{(data?.data ?? []).map(user => (
<li key={user.id}>
{user.name} ({user.role})
</li>
))}
</ul>
);
}
useDatabaseInsert
Insert data into a database table.
Basic Insert
import { useDatabaseInsert } from '@ductape/react';
import { useState } from 'react';
import { session } from './config';
function CreateUser() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const { mutate, isLoading, error } = useDatabaseInsert({
onSuccess: (data) => {
console.log('User created:', data);
setName('');
setEmail('');
alert('User created successfully!');
},
onError: (err) => {
alert('Failed to create user: ' + err.message);
}
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({
table: 'users',
data: { name, email, role: 'user' },
session,
});
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
required
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
type="email"
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create User'}
</button>
{error && <p className="error">{error.message}</p>}
</form>
);
}
Bulk Insert
import { session } from './config';
function BulkUserImport() {
const { mutate, isLoading } = useDatabaseInsert({
onSuccess: (data) => {
alert(`Imported users`);
}
});
const handleImport = () => {
const users = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' }
];
mutate({
table: 'users',
data: users,
session,
});
};
return (
<button onClick={handleImport} disabled={isLoading}>
{isLoading ? 'Importing...' : 'Import Users'}
</button>
);
}
With Optimistic Updates
import { session } from './config';
function CreateTodo() {
const [text, setText] = useState('');
const { mutate } = useDatabaseInsert({
onMutate: async (variables) => {
const newTodo = {
id: `temp-${Date.now()}`,
text: (variables.data as any).text,
completed: false
};
return { newTodo };
},
onSuccess: (data, variables, context) => {
console.log('Todo created:', data);
},
onError: (error, variables, context) => {
console.error('Failed, rolling back:', error);
}
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({
table: 'todos',
data: { text, completed: false },
session,
});
setText('');
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="New todo"
/>
<button type="submit">Add</button>
</form>
);
}
useDatabaseUpdate
Update existing records in a database table. Include session when using a publishable key.
import { useDatabaseUpdate } from '@ductape/react';
import { session } from './config';
function UpdateUserForm({ userId }: { userId: string }) {
const [name, setName] = useState('');
const { mutate, isLoading, error } = useDatabaseUpdate({
onSuccess: () => {
alert('User updated!');
}
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
mutate({
table: 'users',
where: { id: userId },
data: { name, updatedAt: new Date() },
session,
});
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="New name"
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Updating...' : 'Update'}
</button>
{error && <p>{error.message}</p>}
</form>
);
}
Toggle Todo Example
import { session } from './config';
function TodoItem({ todo }: { todo: any }) {
const { mutate } = useDatabaseUpdate({
onSuccess: () => {
console.log('Todo updated');
}
});
const handleToggle = () => {
mutate({
table: 'todos',
where: { id: todo.id },
data: { completed: !todo.completed },
session,
});
};
return (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={handleToggle}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
</div>
);
}
useDatabaseDelete
Delete records from a database table. Include session when using a publishable key.
import { useDatabaseDelete } from '@ductape/react';
import { session } from './config';
function DeleteUserButton({ userId }: { userId: string }) {
const { mutate, isLoading } = useDatabaseDelete({
onSuccess: () => {
alert('User deleted');
}
});
const handleDelete = () => {
if (confirm('Are you sure?')) {
mutate({
table: 'users',
where: { id: userId },
session,
});
}
};
return (
<button onClick={handleDelete} disabled={isLoading}>
{isLoading ? 'Deleting...' : 'Delete'}
</button>
);
}
Bulk Delete
import { session } from './config';
function DeleteCompleted() {
const { mutate, isLoading } = useDatabaseDelete({
onSuccess: (result) => {
alert(`Deleted completed todos`);
}
});
const handleDeleteCompleted = () => {
if (confirm('Delete all completed todos?')) {
mutate({
table: 'todos',
where: { completed: true },
session,
});
}
};
return (
<button onClick={handleDeleteCompleted} disabled={isLoading}>
{isLoading ? 'Deleting...' : 'Delete Completed'}
</button>
);
}
useDatabaseSubscription
Subscribe to real-time database changes. Include session when using a publishable key.
import { useDatabaseSubscription } from '@ductape/react';
import { useState } from 'react';
import { session } from './config';
function LiveMessages() {
const [messages, setMessages] = useState([]);
useDatabaseSubscription({
table: 'messages',
where: { channel: 'general' },
session,
onChange: (event) => {
if (event.type === 'insert') {
setMessages(prev => [...prev, event.data.new]);
} else if (event.type === 'update') {
setMessages(prev =>
prev.map(msg =>
msg.id === event.data.new.id ? event.data.new : msg
)
);
} else if (event.type === 'delete') {
setMessages(prev =>
prev.filter(msg => msg.id !== event.data.old.id)
);
}
}
});
return (
<ul>
{messages.map(msg => (
<li key={msg.id}>{msg.text}</li>
))}
</ul>
);
}
Subscription with Initial Data
import { session } from './config';
function RealtimeTodoList() {
const { data: initialData, isLoading } = useDatabaseQuery(
'todos',
{
table: 'todos',
where: { completed: false },
session,
}
);
const [todos, setTodos] = useState([]);
useEffect(() => {
if (initialData?.data) {
setTodos(initialData.data);
}
}, [initialData]);
useDatabaseSubscription({
table: 'todos',
where: { completed: false },
session,
onChange: (event) => {
if (event.type === 'insert') {
setTodos(prev => [...prev, event.data.new]);
}
}
});
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
Complete CRUD Example
import { session } from './config';
interface Todo {
id: string;
text: string;
completed: boolean;
}
function TodoApp() {
const [newTodoText, setNewTodoText] = useState('');
const { data, isLoading, refetch } = useDatabaseQuery<Todo>(
'todos',
{
table: 'todos',
orderBy: [{ column: 'createdAt', order: 'desc' }],
session,
}
);
const { mutate: createTodo } = useDatabaseInsert({
onSuccess: () => {
refetch();
setNewTodoText('');
}
});
const { mutate: updateTodo } = useDatabaseUpdate({
onSuccess: () => refetch()
});
const { mutate: deleteTodo } = useDatabaseDelete({
onSuccess: () => refetch()
});
const handleCreate = (e: React.FormEvent) => {
e.preventDefault();
createTodo({
table: 'todos',
data: { text: newTodoText, completed: false },
session,
});
};
const handleToggle = (todo: Todo) => {
updateTodo({
table: 'todos',
where: { id: todo.id },
data: { completed: !todo.completed },
session,
});
};
const handleDelete = (todoId: string) => {
if (confirm('Delete this todo?')) {
deleteTodo({
table: 'todos',
where: { id: todoId },
session,
});
}
};
if (isLoading) return <div>Loading todos...</div>;
const rows = data?.data ?? [];
return (
<div>
<form onSubmit={handleCreate}>
<input
value={newTodoText}
onChange={(e) => setNewTodoText(e.target.value)}
placeholder="New todo"
required
/>
<button type="submit">Add</button>
</form>
<ul>
{rows.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleToggle(todo)}
/>
<span
style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}
>
{todo.text}
</span>
<button onClick={() => handleDelete(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
Next Steps
Vue 3 composables for working with Ductape databases. These provide reactive state management and automatic cleanup.
useDatabaseQuery
Query data from a database table with reactive state.
Basic Query
<script setup lang="ts">
import { useDatabaseQuery } from '@ductape/vue';
const sessionToken = useSessionToken(); // from your auth
const { data, isLoading, error, refetch } = useDatabaseQuery('users', {
table: 'users',
limit: 10,
session: sessionToken,
});
</script>
<template>
<div>
<button @click="refetch">Refresh</button>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<ul v-else>
<li v-for="user in data?.rows" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>
Query with Filters
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useDatabaseQuery } from '@ductape/vue';
const category = ref('electronics');
const priceLimit = ref(1000);
const sessionToken = useSessionToken();
const queryOptions = computed(() => ({
table: 'products',
where: {
category: category.value,
price: { $lt: priceLimit.value }
},
orderBy: [{ column: 'price', order: 'asc' }],
session: sessionToken.value,
}));
const { data, isLoading } = useDatabaseQuery(
computed(() => ['products', category.value, priceLimit.value]),
queryOptions
);
</script>
<template>
<div>
<select v-model="category">
<option value="electronics">Electronics</option>
<option value="books">Books</option>
<option value="clothing">Clothing</option>
</select>
<input
v-model.number="priceLimit"
type="number"
placeholder="Max price"
/>
<div v-if="isLoading">Loading products...</div>
<div v-else class="product-grid">
<div v-for="product in data?.rows" :key="product.id" class="product-card">
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
</div>
</div>
</div>
</template>
useDatabaseInsert
Insert data into a database table.
<script setup lang="ts">
import { ref } from 'vue';
import { useDatabaseInsert } from '@ductape/vue';
const name = ref('');
const email = ref('');
const sessionToken = useSessionToken();
const { mutate, isLoading, error } = useDatabaseInsert({
onSuccess: (data) => {
console.log('User created:', data.rows[0]);
name.value = '';
email.value = '';
alert('User created successfully!');
}
});
const handleSubmit = () => {
mutate({
table: 'users',
data: {
name: name.value,
email: email.value,
role: 'user'
},
session: sessionToken.value,
});
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<input
v-model="name"
placeholder="Name"
required
/>
<input
v-model="email"
type="email"
placeholder="Email"
required
/>
<button type="submit" :disabled="isLoading">
{{ isLoading ? 'Creating...' : 'Create User' }}
</button>
<p v-if="error" class="error">{{ error.message }}</p>
</form>
</template>
useDatabaseUpdate
Update existing records in a database table.
<script setup lang="ts">
import { ref } from 'vue';
import { useDatabaseUpdate } from '@ductape/vue';
const props = defineProps<{ userId: string }>();
const name = ref('');
const { mutate, isLoading } = useDatabaseUpdate({
onSuccess: () => {
alert('User updated!');
}
});
const handleUpdate = () => {
mutate({
table: 'users',
where: { id: props.userId },
data: {
name: name.value,
updatedAt: new Date()
}
});
};
</script>
<template>
<form @submit.prevent="handleUpdate">
<input
v-model="name"
placeholder="New name"
/>
<button type="submit" :disabled="isLoading">
{{ isLoading ? 'Updating...' : 'Update' }}
</button>
</form>
</template>
Toggle Example
<script setup lang="ts">
import { useDatabaseUpdate } from '@ductape/vue';
const props = defineProps<{ todo: any }>();
const { mutate } = useDatabaseUpdate({
onSuccess: () => {
console.log('Todo updated');
}
});
const handleToggle = () => {
mutate({
table: 'todos',
where: { id: props.todo.id },
data: { completed: !props.todo.completed }
});
};
</script>
<template>
<div>
<input
type="checkbox"
:checked="todo.completed"
@change="handleToggle"
/>
<span :class="{ 'line-through': todo.completed }">
{{ todo.text }}
</span>
</div>
</template>
useDatabaseDelete
Delete records from a database table.
<script setup lang="ts">
import { useDatabaseDelete } from '@ductape/vue';
const props = defineProps<{ userId: string }>();
const { mutate, isLoading } = useDatabaseDelete({
onSuccess: () => {
alert('User deleted');
}
});
const handleDelete = () => {
if (confirm('Are you sure?')) {
mutate({
table: 'users',
where: { id: props.userId }
});
}
};
</script>
<template>
<button @click="handleDelete" :disabled="isLoading">
{{ isLoading ? 'Deleting...' : 'Delete' }}
</button>
</template>
useDatabaseSubscription
Subscribe to real-time database changes.
<script setup lang="ts">
import { ref } from 'vue';
import { useDatabaseSubscription } from '@ductape/vue';
const messages = ref<any[]>([]);
useDatabaseSubscription({
table: 'messages',
where: { channel: 'general' },
onChange: (event) => {
if (event.type === 'insert') {
messages.value.push(event.data.new);
} else if (event.type === 'update') {
const index = messages.value.findIndex(m => m.id === event.data.new.id);
if (index !== -1) {
messages.value[index] = event.data.new;
}
} else if (event.type === 'delete') {
messages.value = messages.value.filter(m => m.id !== event.data.old.id);
}
}
});
</script>
<template>
<ul>
<li v-for="msg in messages" :key="msg.id">
{{ msg.text }}
</li>
</ul>
</template>
Complete CRUD Example
<script setup lang="ts">
import { ref } from 'vue';
import {
useDatabaseQuery,
useDatabaseInsert,
useDatabaseUpdate,
useDatabaseDelete
} from '@ductape/vue';
interface Todo {
id: string;
text: string;
completed: boolean;
}
const newTodoText = ref('');
// Query todos
const { data, isLoading, refetch } = useDatabaseQuery<Todo>('todos', {
table: 'todos',
orderBy: [{ column: 'createdAt', order: 'desc' }]
});
// Create mutation
const { mutate: createTodo } = useDatabaseInsert({
onSuccess: () => {
refetch();
newTodoText.value = '';
}
});
// Update mutation
const { mutate: updateTodo } = useDatabaseUpdate({
onSuccess: () => refetch()
});
// Delete mutation
const { mutate: deleteTodo } = useDatabaseDelete({
onSuccess: () => refetch()
});
const handleCreate = () => {
createTodo({
table: 'todos',
data: {
text: newTodoText.value,
completed: false
}
});
};
const handleToggle = (todo: Todo) => {
updateTodo({
table: 'todos',
where: { id: todo.id },
data: { completed: !todo.completed }
});
};
const handleDelete = (todoId: string) => {
if (confirm('Delete this todo?')) {
deleteTodo({
table: 'todos',
where: { id: todoId }
});
}
};
</script>
<template>
<div>
<form @submit.prevent="handleCreate">
<input
v-model="newTodoText"
placeholder="New todo"
required
/>
<button type="submit">Add</button>
</form>
<div v-if="isLoading">Loading todos...</div>
<ul v-else>
<li v-for="todo in data?.rows" :key="todo.id">
<input
type="checkbox"
:checked="todo.completed"
@change="handleToggle(todo)"
/>
<span :class="{ 'line-through': todo.completed }">
{{ todo.text }}
</span>
<button @click="handleDelete(todo.id)">Delete</button>
</li>
</ul>
</div>
</template>
<style scoped>
.line-through {
text-decoration: line-through;
}
</style>