Skip to content

Framework Integration

The OstrichDB SDK works seamlessly with popular JavaScript frameworks and libraries. This guide provides comprehensive examples and best practices for integrating OstrichDB into your applications.

app.ts
import express from 'express';
import OstrichDB from 'ostrichdb-js';
const app = express();
app.use(express.json());
// Database configuration
const db = new OstrichDB({
baseUrl: process.env.OSTRICHDB_URL || 'http://localhost:8042',
apiKey: process.env.OSTRICHDB_TOKEN,
timeout: 15000
});
// Health check endpoint
app.get('/health', async (req, res) => {
try {
await db.health_check();
res.json({ status: 'healthy', database: 'connected' });
} catch (error) {
res.status(503).json({ status: 'unhealthy', error: error.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
routes/users.ts
import express from 'express';
import OstrichDB from 'ostrichdb-js';
const router = express.Router();
// Middleware to ensure database connection
function requireDatabase(req: any, res: any, next: any) {
const db = new OstrichDB({
baseUrl: process.env.OSTRICHDB_URL,
apiKey: req.headers.authorization?.replace('Bearer ', '')
});
if (!db.apiKey) {
return res.status(401).json({ error: 'Authorization token required' });
}
req.db = db;
next();
}
router.use(requireDatabase);
// Create user
router.post('/', async (req, res) => {
try {
const { username, email, age } = req.body;
const userId = `user-${Date.now()}`;
const users = req.db.project('app').collection('users').cluster('active');
// Create user records
await users.record(`${userId}-username`, 'STRING', username)
.create(`${userId}-username`, 'STRING', username);
await users.record(`${userId}-email`, 'STRING', email)
.create(`${userId}-email`, 'STRING', email);
await users.record(`${userId}-age`, 'INTEGER', age.toString())
.create(`${userId}-age`, 'INTEGER', age.toString());
await users.record(`${userId}-created`, 'DATETIME', new Date().toISOString())
.create(`${userId}-created`, 'DATETIME', new Date().toISOString());
res.status(201).json({
success: true,
userId,
message: 'User created successfully'
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get user
router.get('/:userId', async (req, res) => {
try {
const { userId } = req.params;
const users = req.db.project('app').collection('users').cluster('active');
const username = await users.record(`${userId}-username`).get(`${userId}-username`);
const email = await users.record(`${userId}-email`).get(`${userId}-email`);
const age = await users.record(`${userId}-age`).get(`${userId}-age`);
const created = await users.record(`${userId}-created`).get(`${userId}-created`);
res.json({
id: userId,
username: username.value,
email: email.value,
age: parseInt(age.value),
created: created.value
});
} catch (error) {
if (error.statusCode === 404) {
res.status(404).json({ error: 'User not found' });
} else {
res.status(500).json({ error: error.message });
}
}
});
// List users
router.get('/', async (req, res) => {
try {
const users = req.db.project('app').collection('users').cluster('active');
const records = await users.listRecords();
// Group records by user ID
const userMap = new Map();
records.forEach(record => {
const parts = record.name.split('-');
const userId = parts.slice(0, 2).join('-');
const field = parts.slice(2).join('-');
if (!userMap.has(userId)) {
userMap.set(userId, { id: userId });
}
userMap.get(userId)[field] = record.value;
});
const userList = Array.from(userMap.values())
.filter(user => user.username) // Only complete users
.map(user => ({
...user,
age: user.age ? parseInt(user.age) : null
}));
res.json({ users: userList, count: userList.length });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Delete user
router.delete('/:userId', async (req, res) => {
try {
const { userId } = req.params;
const users = req.db.project('app').collection('users').cluster('active');
// Delete all user records
await users.record(`${userId}-username`).delete(`${userId}-username`);
await users.record(`${userId}-email`).delete(`${userId}-email`);
await users.record(`${userId}-age`).delete(`${userId}-age`);
await users.record(`${userId}-created`).delete(`${userId}-created`);
res.json({ success: true, message: 'User deleted successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;
routes/blog.ts
import express from 'express';
import { z } from 'zod'; // For validation
import OstrichDB from 'ostrichdb-js';
const router = express.Router();
// Validation schemas
const createPostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
author: z.string().min(1).max(100),
tags: z.array(z.string()).optional().default([])
});
// Database middleware
router.use((req: any, res, next) => {
req.db = new OstrichDB({
baseUrl: process.env.OSTRICHDB_URL,
apiKey: req.headers.authorization?.replace('Bearer ', ''),
timeout: 10000
});
next();
});
// Create blog post
router.post('/posts', async (req, res) => {
try {
const postData = createPostSchema.parse(req.body);
const postId = `post-${Date.now()}`;
const posts = req.db.project('blog').collection('content').cluster('published');
// Create post using builder pattern
await posts.record(`${postId}-title`, 'STRING', postData.title)
.create(`${postId}-title`, 'STRING', postData.title);
await posts.record(`${postId}-content`, 'STRING', postData.content)
.create(`${postId}-content`, 'STRING', postData.content);
await posts.record(`${postId}-author`, 'STRING', postData.author)
.create(`${postId}-author`, 'STRING', postData.author);
await posts.record(`${postId}-tags`, '[]STRING', JSON.stringify(postData.tags))
.create(`${postId}-tags`, '[]STRING', JSON.stringify(postData.tags));
await posts.record(`${postId}-published`, 'DATETIME', new Date().toISOString())
.create(`${postId}-published`, 'DATETIME', new Date().toISOString());
res.status(201).json({
success: true,
postId,
title: postData.title
});
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ error: 'Validation failed', details: error.errors });
} else {
res.status(500).json({ error: error.message });
}
}
});
// Get blog posts with pagination
router.get('/posts', async (req, res) => {
try {
const page = parseInt(req.query.page as string) || 1;
const limit = parseInt(req.query.limit as string) || 10;
const offset = (page - 1) * limit;
const posts = req.db.project('blog').collection('content').cluster('published');
// Get paginated records
const records = await posts.searchRecords({
sortBy: 'name',
sortOrder: 'desc',
limit,
offset
});
// Group by post ID and build complete posts
const postMap = new Map();
records.forEach(record => {
const parts = record.name.split('-');
const postId = parts.slice(0, 2).join('-');
const field = parts.slice(2).join('-');
if (!postMap.has(postId)) {
postMap.set(postId, { id: postId });
}
if (field === 'tags') {
postMap.get(postId)[field] = JSON.parse(record.value);
} else {
postMap.get(postId)[field] = record.value;
}
});
const postList = Array.from(postMap.values())
.filter(post => post.title && post.content);
res.json({
posts: postList,
pagination: {
page,
limit,
hasMore: postList.length === limit
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;
app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
import OstrichDB from 'ostrichdb-js';
// Initialize database client
function createDBClient(request: NextRequest) {
const token = request.headers.get('authorization')?.replace('Bearer ', '');
return new OstrichDB({
baseUrl: process.env.OSTRICHDB_URL!,
apiKey: token || process.env.OSTRICHDB_TOKEN!,
timeout: 10000
});
}
export async function GET(request: NextRequest) {
try {
const db = createDBClient(request);
const { searchParams } = new URL(request.url);
const category = searchParams.get('category');
const products = db.project('ecommerce').collection('inventory').cluster('products');
let records;
if (category) {
records = await products.searchRecords({
search: 'category',
valueContains: category,
limit: 50
});
} else {
records = await products.listRecords();
}
// Group records by product ID
const productMap = new Map();
records.forEach(record => {
const parts = record.name.split('-');
const productId = parts.slice(0, 2).join('-');
const field = parts.slice(2).join('-');
if (!productMap.has(productId)) {
productMap.set(productId, { id: productId });
}
productMap.get(productId)[field] = record.value;
});
const productList = Array.from(productMap.values())
.filter(product => product.name && product.price)
.map(product => ({
...product,
price: parseFloat(product.price)
}));
return NextResponse.json({ products: productList });
} catch (error) {
console.error('Products API error:', error);
return NextResponse.json(
{ error: 'Failed to fetch products' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const db = createDBClient(request);
const body = await request.json();
const { name, price, category, description } = body;
// Validation
if (!name || !price || !category) {
return NextResponse.json(
{ error: 'Name, price, and category are required' },
{ status: 400 }
);
}
const productId = `product-${Date.now()}`;
const products = db.project('ecommerce').collection('inventory').cluster('products');
// Create product using builder pattern
await products.record(`${productId}-name`, 'STRING', name)
.create(`${productId}-name`, 'STRING', name);
await products.record(`${productId}-price`, 'FLOAT', price.toString())
.create(`${productId}-price`, 'FLOAT', price.toString());
await products.record(`${productId}-category`, 'STRING', category)
.create(`${productId}-category`, 'STRING', category);
if (description) {
await products.record(`${productId}-description`, 'STRING', description)
.create(`${productId}-description`, 'STRING', description);
}
await products.record(`${productId}-created`, 'DATETIME', new Date().toISOString())
.create(`${productId}-created`, 'DATETIME', new Date().toISOString());
return NextResponse.json({
success: true,
productId,
name
}, { status: 201 });
} catch (error) {
console.error('Create product error:', error);
return NextResponse.json(
{ error: 'Failed to create product' },
{ status: 500 }
);
}
}
pages/api/users/[userId].ts
import type { NextApiRequest, NextApiResponse } from 'next';
import OstrichDB from 'ostrichdb-js';
const db = new OstrichDB({
baseUrl: process.env.OSTRICHDB_URL!,
apiKey: process.env.OSTRICHDB_TOKEN!,
timeout: 15000
});
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { userId } = req.query;
if (typeof userId !== 'string') {
return res.status(400).json({ error: 'Invalid user ID' });
}
const users = db.project('social-app').collection('profiles').cluster('users');
try {
switch (req.method) {
case 'GET':
const username = await users.record(`${userId}-username`).get(`${userId}-username`);
const email = await users.record(`${userId}-email`).get(`${userId}-email`);
const bio = await users.record(`${userId}-bio`).get(`${userId}-bio`);
const joined = await users.record(`${userId}-joined`).get(`${userId}-joined`);
res.json({
id: userId,
username: username.value,
email: email.value,
bio: bio.value,
joined: joined.value
});
break;
case 'PUT':
const { username: newUsername, email: newEmail, bio: newBio } = req.body;
if (newUsername) {
await users.record(`${userId}-username`, 'STRING', newUsername)
.create(`${userId}-username`, 'STRING', newUsername);
}
if (newEmail) {
await users.record(`${userId}-email`, 'STRING', newEmail)
.create(`${userId}-email`, 'STRING', newEmail);
}
if (newBio) {
await users.record(`${userId}-bio`, 'STRING', newBio)
.create(`${userId}-bio`, 'STRING', newBio);
}
res.json({ success: true, message: 'User updated successfully' });
break;
case 'DELETE':
await users.record(`${userId}-username`).delete(`${userId}-username`);
await users.record(`${userId}-email`).delete(`${userId}-email`);
await users.record(`${userId}-bio`).delete(`${userId}-bio`);
await users.record(`${userId}-joined`).delete(`${userId}-joined`);
res.json({ success: true, message: 'User deleted successfully' });
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
} catch (error) {
console.error('User API error:', error);
if (error.statusCode === 404) {
res.status(404).json({ error: 'User not found' });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
}
app/dashboard/actions.ts
'use server'
import OstrichDB from 'ostrichdb-js';
import { revalidatePath } from 'next/cache';
const db = new OstrichDB({
baseUrl: process.env.OSTRICHDB_URL!,
apiKey: process.env.OSTRICHDB_TOKEN!
});
export async function createTask(formData: FormData) {
try {
const title = formData.get('title') as string;
const description = formData.get('description') as string;
const priority = formData.get('priority') as string;
if (!title) {
throw new Error('Title is required');
}
const taskId = `task-${Date.now()}`;
const tasks = db.project('productivity').collection('tasks').cluster('active');
await tasks.record(`${taskId}-title`, 'STRING', title)
.create(`${taskId}-title`, 'STRING', title);
await tasks.record(`${taskId}-description`, 'STRING', description || '')
.create(`${taskId}-description`, 'STRING', description || '');
await tasks.record(`${taskId}-priority`, 'STRING', priority)
.create(`${taskId}-priority`, 'STRING', priority);
await tasks.record(`${taskId}-status`, 'STRING', 'pending')
.create(`${taskId}-status`, 'STRING', 'pending');
await tasks.record(`${taskId}-created`, 'DATETIME', new Date().toISOString())
.create(`${taskId}-created`, 'DATETIME', new Date().toISOString());
revalidatePath('/dashboard');
return { success: true, taskId };
} catch (error) {
console.error('Create task error:', error);
return { success: false, error: error.message };
}
}
export async function updateTaskStatus(taskId: string, status: string) {
try {
const tasks = db.project('productivity').collection('tasks').cluster('active');
await tasks.record(`${taskId}-status`, 'STRING', status)
.create(`${taskId}-status`, 'STRING', status);
if (status === 'completed') {
await tasks.record(`${taskId}-completed`, 'DATETIME', new Date().toISOString())
.create(`${taskId}-completed`, 'DATETIME', new Date().toISOString());
}
revalidatePath('/dashboard');
return { success: true };
} catch (error) {
console.error('Update task error:', error);
return { success: false, error: error.message };
}
}
export async function getTasks() {
try {
const tasks = db.project('productivity').collection('tasks').cluster('active');
const records = await tasks.listRecords();
// Group records by task ID
const taskMap = new Map();
records.forEach(record => {
const parts = record.name.split('-');
const taskId = parts.slice(0, 2).join('-');
const field = parts.slice(2).join('-');
if (!taskMap.has(taskId)) {
taskMap.set(taskId, { id: taskId });
}
taskMap.get(taskId)[field] = record.value;
});
return Array.from(taskMap.values())
.filter(task => task.title)
.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime());
} catch (error) {
console.error('Get tasks error:', error);
return [];
}
}
hooks/useOstrichDB.ts
import { useState, useEffect, useCallback } from 'react';
import OstrichDB from 'ostrichdb-js';
interface UseOstrichDBOptions {
baseUrl?: string;
apiKey?: string;
timeout?: number;
}
export function useOstrichDB(options: UseOstrichDBOptions) {
const [db, setDb] = useState<OstrichDB | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const initializeDB = async () => {
try {
const client = new OstrichDB({
baseUrl: options.baseUrl || process.env.REACT_APP_OSTRICHDB_URL!,
apiKey: options.apiKey || process.env.REACT_APP_OSTRICHDB_TOKEN!,
timeout: options.timeout || 10000
});
// Test connection
await client.health_check();
setDb(client);
setIsConnected(true);
setError(null);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to connect';
setError(errorMessage);
setIsConnected(false);
console.error('OstrichDB connection error:', err);
}
};
if (options.apiKey) {
initializeDB();
}
}, [options.apiKey, options.baseUrl, options.timeout]);
const updateToken = useCallback((newToken: string) => {
if (db) {
db.setAuthToken(newToken);
setError(null);
}
}, [db]);
return {
db,
isConnected,
error,
updateToken
};
}
components/TaskManager.tsx
import React, { useState, useEffect } from 'react';
import { useOstrichDB } from '../hooks/useOstrichDB';
interface Task {
id: string;
title: string;
description: string;
status: 'pending' | 'in-progress' | 'completed';
created: string;
}
export function TaskManager() {
const { db, isConnected, error } = useOstrichDB({
apiKey: localStorage.getItem('authToken') || ''
});
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(false);
const [newTask, setNewTask] = useState({ title: '', description: '' });
const loadTasks = async () => {
if (!db || !isConnected) return;
setLoading(true);
try {
const taskCluster = db.project('productivity')
.collection('tasks')
.cluster('active');
const records = await taskCluster.listRecords();
// Group records by task ID
const taskMap = new Map();
records.forEach(record => {
const parts = record.name.split('-');
const taskId = parts.slice(0, 2).join('-');
const field = parts.slice(2).join('-');
if (!taskMap.has(taskId)) {
taskMap.set(taskId, { id: taskId });
}
taskMap.get(taskId)[field] = record.value;
});
const taskList = Array.from(taskMap.values())
.filter(task => task.title)
.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime());
setTasks(taskList);
} catch (err) {
console.error('Failed to load tasks:', err);
} finally {
setLoading(false);
}
};
const createTask = async () => {
if (!db || !newTask.title.trim()) return;
try {
const taskId = `task-${Date.now()}`;
const taskCluster = db.project('productivity')
.collection('tasks')
.cluster('active');
await taskCluster.record(`${taskId}-title`, 'STRING', newTask.title)
.create(`${taskId}-title`, 'STRING', newTask.title);
await taskCluster.record(`${taskId}-description`, 'STRING', newTask.description)
.create(`${taskId}-description`, 'STRING', newTask.description);
await taskCluster.record(`${taskId}-status`, 'STRING', 'pending')
.create(`${taskId}-status`, 'STRING', 'pending');
await taskCluster.record(`${taskId}-created`, 'DATETIME', new Date().toISOString())
.create(`${taskId}-created`, 'DATETIME', new Date().toISOString());
setNewTask({ title: '', description: '' });
await loadTasks(); // Refresh task list
} catch (err) {
console.error('Failed to create task:', err);
}
};
const updateTaskStatus = async (taskId: string, newStatus: string) => {
if (!db) return;
try {
const taskCluster = db.project('productivity')
.collection('tasks')
.cluster('active');
await taskCluster.record(`${taskId}-status`, 'STRING', newStatus)
.create(`${taskId}-status`, 'STRING', newStatus);
await loadTasks(); // Refresh task list
} catch (err) {
console.error('Failed to update task:', err);
}
};
useEffect(() => {
if (isConnected) {
loadTasks();
}
}, [isConnected]);
if (error) {
return <div className="error">Error: {error}</div>;
}
if (!isConnected) {
return <div>Connecting to OstrichDB...</div>;
}
return (
<div className="task-manager">
<h2>Task Manager</h2>
{/* Create new task */}
<div className="create-task">
<input
type="text"
placeholder="Task title"
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
/>
<textarea
placeholder="Task description"
value={newTask.description}
onChange={(e) => setNewTask({ ...newTask, description: e.target.value })}
/>
<button onClick={createTask} disabled={!newTask.title.trim()}>
Create Task
</button>
</div>
{/* Task list */}
{loading ? (
<div>Loading tasks...</div>
) : (
<div className="task-list">
{tasks.map(task => (
<div key={task.id} className={`task task-${task.status}`}>
<h3>{task.title}</h3>
<p>{task.description}</p>
<div className="task-meta">
<span>Status: {task.status}</span>
<span>Created: {new Date(task.created).toLocaleDateString()}</span>
</div>
<div className="task-actions">
{task.status === 'pending' && (
<button onClick={() => updateTaskStatus(task.id, 'in-progress')}>
Start
</button>
)}
{task.status === 'in-progress' && (
<button onClick={() => updateTaskStatus(task.id, 'completed')}>
Complete
</button>
)}
</div>
</div>
))}
</div>
)}
</div>
);
}
composables/useOstrichDB.ts
import { ref, onMounted, computed } from 'vue';
import OstrichDB from 'ostrichdb-js';
export function useOstrichDB(options: {
baseUrl?: string;
apiKey?: string;
timeout?: number;
}) {
const db = ref<OstrichDB | null>(null);
const isConnected = ref(false);
const error = ref<string | null>(null);
const loading = ref(false);
const initialize = async () => {
loading.value = true;
try {
const client = new OstrichDB({
baseUrl: options.baseUrl || import.meta.env.VITE_OSTRICHDB_URL,
apiKey: options.apiKey || import.meta.env.VITE_OSTRICHDB_TOKEN,
timeout: options.timeout || 10000
});
await client.health_check();
db.value = client;
isConnected.value = true;
error.value = null;
} catch (err) {
error.value = err instanceof Error ? err.message : 'Connection failed';
isConnected.value = false;
} finally {
loading.value = false;
}
};
const updateToken = (newToken: string) => {
if (db.value) {
db.value.setAuthToken(newToken);
error.value = null;
}
};
onMounted(() => {
if (options.apiKey) {
initialize();
}
});
return {
db: computed(() => db.value),
isConnected: computed(() => isConnected.value),
error: computed(() => error.value),
loading: computed(() => loading.value),
initialize,
updateToken
};
}
components/ProductCatalog.vue
<template>
<div class="product-catalog">
<h2>Product Catalog</h2>
<div v-if="loading" class="loading">
Loading products...
</div>
<div v-else-if="error" class="error">
Error: {{ error }}
</div>
<div v-else>
<!-- Add product form -->
<form @submit.prevent="addProduct" class="add-product">
<input
v-model="newProduct.name"
type="text"
placeholder="Product name"
required
/>
<input
v-model.number="newProduct.price"
type="number"
step="0.01"
placeholder="Price"
required
/>
<select v-model="newProduct.category" required>
<option value="">Select category</option>
<option value="Electronics">Electronics</option>
<option value="Clothing">Clothing</option>
<option value="Books">Books</option>
</select>
<button type="submit" :disabled="!isFormValid">
Add Product
</button>
</form>
<!-- Product list -->
<div class="product-grid">
<div
v-for="product in products"
:key="product.id"
class="product-card"
>
<h3>{{ product.name }}</h3>
<p class="price">${{ product.price.toFixed(2) }}</p>
<p class="category">{{ product.category }}</p>
<button @click="deleteProduct(product.id)" class="delete-btn">
Delete
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useOstrichDB } from '../composables/useOstrichDB';
interface Product {
id: string;
name: string;
price: number;
category: string;
created: string;
}
const { db, isConnected, error } = useOstrichDB({
apiKey: localStorage.getItem('authToken') || ''
});
const products = ref<Product[]>([]);
const loading = ref(false);
const newProduct = ref({
name: '',
price: 0,
category: ''
});
const isFormValid = computed(() => {
return newProduct.value.name.trim() &&
newProduct.value.price > 0 &&
newProduct.value.category;
});
const loadProducts = async () => {
if (!db.value || !isConnected.value) return;
loading.value = true;
try {
const productCluster = db.value.project('store')
.collection('inventory')
.cluster('products');
const records = await productCluster.listRecords();
// Group records by product ID
const productMap = new Map();
records.forEach(record => {
const parts = record.name.split('-');
const productId = parts.slice(0, 2).join('-');
const field = parts.slice(2).join('-');
if (!productMap.has(productId)) {
productMap.set(productId, { id: productId });
}
productMap.get(productId)[field] = record.value;
});
products.value = Array.from(productMap.values())
.filter(product => product.name && product.price)
.map(product => ({
...product,
price: parseFloat(product.price)
}));
} catch (err) {
console.error('Failed to load products:', err);
} finally {
loading.value = false;
}
};
const addProduct = async () => {
if (!db.value || !isFormValid.value) return;
try {
const productId = `product-${Date.now()}`;
const productCluster = db.value.project('store')
.collection('inventory')
.cluster('products');
await productCluster.record(`${productId}-name`, 'STRING', newProduct.value.name)
.create(`${productId}-name`, 'STRING', newProduct.value.name);
await productCluster.record(`${productId}-price`, 'FLOAT', newProduct.value.price.toString())
.create(`${productId}-price`, 'FLOAT', newProduct.value.price.toString());
await productCluster.record(`${productId}-category`, 'STRING', newProduct.value.category)
.create(`${productId}-category`, 'STRING', newProduct.value.category);
await productCluster.record(`${productId}-created`, 'DATETIME', new Date().toISOString())
.create(`${productId}-created`, 'DATETIME', new Date().toISOString());
// Reset form
newProduct.value = { name: '', price: 0, category: '' };
// Reload products
await loadProducts();
} catch (err) {
console.error('Failed to add product:', err);
}
};
const deleteProduct = async (productId: string) => {
if (!db.value) return;
if (!confirm('Are you sure you want to delete this product?')) return;
try {
const productCluster = db.value.project('store')
.collection('inventory')
.cluster('products');
await productCluster.record(`${productId}-name`).delete(`${productId}-name`);
await productCluster.record(`${productId}-price`).delete(`${productId}-price`);
await productCluster.record(`${productId}-category`).delete(`${productId}-category`);
await productCluster.record(`${productId}-created`).delete(`${productId}-created`);
await loadProducts();
} catch (err) {
console.error('Failed to delete product:', err);
}
};
onMounted(() => {
if (isConnected.value) {
loadProducts();
}
});
// Watch for connection changes
watch(isConnected, (connected) => {
if (connected) {
loadProducts();
}
});
</script>

Singleton Pattern:

utils/database.ts
import OstrichDB from 'ostrichdb-js';
let dbInstance: OstrichDB | null = null;
export function getDatabase(): OstrichDB {
if (!dbInstance) {
dbInstance = new OstrichDB({
baseUrl: process.env.OSTRICHDB_URL!,
apiKey: process.env.OSTRICHDB_TOKEN!,
timeout: 15000
});
}
return dbInstance;
}
export function updateDatabaseToken(token: string) {
if (dbInstance) {
dbInstance.setAuthToken(token);
}
}

Centralized Error Handler:

utils/errorHandler.ts
import { OstrichDBError } from 'ostrichdb-js';
export function handleDatabaseError(error: unknown) {
if (error instanceof OstrichDBError) {
switch (error.statusCode) {
case 401:
// Handle authentication error
window.location.href = '/login';
break;
case 403:
console.error('Access denied:', error.message);
break;
case 404:
console.error('Resource not found:', error.message);
break;
case 429:
console.error('Rate limit exceeded:', error.message);
break;
default:
console.error('Database error:', error.message);
}
} else {
console.error('Unexpected error:', error);
}
}

TypeScript Interfaces:

types/database.ts
export interface User {
id: string;
username: string;
email: string;
created: string;
}
export interface Product {
id: string;
name: string;
price: number;
category: string;
description?: string;
created: string;
}
export interface DatabaseResponse<T> {
success: boolean;
data?: T;
error?: string;
}

Multi-environment Setup:

config/database.ts
interface DatabaseConfig {
baseUrl: string;
timeout: number;
}
const configs: Record<string, DatabaseConfig> = {
development: {
baseUrl: 'http://localhost:8042',
timeout: 30000
},
production: {
baseUrl: process.env.OSTRICHDB_URL!,
timeout: 10000
},
test: {
baseUrl: 'http://localhost:8043',
timeout: 5000
}
};
export function getDatabaseConfig(): DatabaseConfig {
const env = process.env.NODE_ENV || 'development';
return configs[env];
}

Now that you understand framework integration:

  1. Error Handling - Implement comprehensive error management
  2. Configuration - Optimize settings for different environments
  3. API Reference - Explore all available methods
  4. Builder Pattern - Master the chainable API

Framework integration makes OstrichDB accessible from any JavaScript environment. Choose the patterns that best fit your application architecture and follow the established conventions of your chosen framework.