Examples
Practical examples showing how to use DuckDB WASM Adapter in different scenarios.
Framework Examples
React Examples
Basic CRUD Application
A complete user management system with Create, Read, Update, Delete operations.
jsx
// src/App.jsx
import { DuckDBProvider } from '@northprint/duckdb-wasm-adapter-react';
import UserManagement from './components/UserManagement';
function App() {
return (
<DuckDBProvider autoConnect>
<div className="App">
<h1>User Management System</h1>
<UserManagement />
</div>
</DuckDBProvider>
);
}
export default App;jsx
// src/components/UserManagement.jsx
import { useState } from 'react';
import { useQuery, useMutation } from '@northprint/duckdb-wasm-adapter-react';
function UserManagement() {
const [formData, setFormData] = useState({
name: '',
email: '',
department: ''
});
// Fetch users
const { data: users, loading, error, refetch } = useQuery(`
SELECT id, name, email, department, created_at
FROM users
ORDER BY created_at DESC
`);
// Create user mutation
const { mutate: createUser, loading: creating } = useMutation({
onSuccess: () => {
setFormData({ name: '', email: '', department: '' });
refetch();
}
});
// Delete user mutation
const { mutate: deleteUser } = useMutation({
onSuccess: () => refetch()
});
const handleSubmit = (e) => {
e.preventDefault();
createUser(
'INSERT INTO users (name, email, department) VALUES (?, ?, ?)',
[formData.name, formData.email, formData.department]
);
};
const handleDelete = (id) => {
if (confirm('Are you sure?')) {
deleteUser('DELETE FROM users WHERE id = ?', [id]);
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{/* Create Form */}
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
placeholder="Name"
required
/>
<input
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
placeholder="Email"
type="email"
required
/>
<select
value={formData.department}
onChange={(e) => setFormData(prev => ({ ...prev, department: e.target.value }))}
required
>
<option value="">Select Department</option>
<option value="Engineering">Engineering</option>
<option value="Sales">Sales</option>
<option value="Marketing">Marketing</option>
</select>
<button type="submit" disabled={creating}>
{creating ? 'Creating...' : 'Create User'}
</button>
</form>
{/* Users List */}
<div>
<h2>Users ({users?.length || 0})</h2>
{users?.map(user => (
<div key={user.id} style={{ border: '1px solid #ddd', padding: '1rem', margin: '0.5rem' }}>
<h3>{user.name}</h3>
<p>Email: {user.email}</p>
<p>Department: {user.department}</p>
<button onClick={() => handleDelete(user.id)}>Delete</button>
</div>
))}
</div>
</div>
);
}
export default UserManagement;Data Analytics Dashboard
Real-time analytics dashboard with charts and metrics.
jsx
// src/components/AnalyticsDashboard.jsx
import { useQuery } from '@northprint/duckdb-wasm-adapter-react';
import { useMemo } from 'react';
function AnalyticsDashboard() {
// Department statistics
const { data: deptStats } = useQuery(`
SELECT
department,
COUNT(*) as employee_count,
AVG(salary) as avg_salary,
MAX(salary) as max_salary,
MIN(salary) as min_salary
FROM employees
GROUP BY department
ORDER BY employee_count DESC
`);
// Monthly hiring trends
const { data: hiringTrends } = useQuery(`
SELECT
DATE_TRUNC('month', hired_date) as month,
COUNT(*) as hires
FROM employees
WHERE hired_date >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY month
ORDER BY month
`);
// Performance metrics
const { data: metrics } = useQuery(`
SELECT
COUNT(*) as total_employees,
AVG(salary) as avg_salary,
COUNT(DISTINCT department) as departments,
AVG(performance_score) as avg_performance
FROM employees
WHERE active = true
`);
const formatCurrency = (value) =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
return (
<div className="dashboard">
{/* Key Metrics */}
<div className="metrics-grid">
<div className="metric-card">
<h3>Total Employees</h3>
<p className="metric-value">{metrics?.[0]?.total_employees || 0}</p>
</div>
<div className="metric-card">
<h3>Average Salary</h3>
<p className="metric-value">{formatCurrency(metrics?.[0]?.avg_salary || 0)}</p>
</div>
<div className="metric-card">
<h3>Departments</h3>
<p className="metric-value">{metrics?.[0]?.departments || 0}</p>
</div>
<div className="metric-card">
<h3>Avg Performance</h3>
<p className="metric-value">{(metrics?.[0]?.avg_performance || 0).toFixed(1)}</p>
</div>
</div>
{/* Department Statistics */}
<div className="chart-section">
<h2>Department Statistics</h2>
<div className="dept-stats">
{deptStats?.map(dept => (
<div key={dept.department} className="dept-card">
<h3>{dept.department}</h3>
<p>Employees: {dept.employee_count}</p>
<p>Avg Salary: {formatCurrency(dept.avg_salary)}</p>
<p>Salary Range: {formatCurrency(dept.min_salary)} - {formatCurrency(dept.max_salary)}</p>
</div>
))}
</div>
</div>
{/* Hiring Trends */}
<div className="chart-section">
<h2>Hiring Trends (Last 12 Months)</h2>
<div className="trend-chart">
{hiringTrends?.map(trend => (
<div key={trend.month} className="trend-bar">
<div className="bar" style={{ height: `${trend.hires * 10}px` }}></div>
<span>{new Date(trend.month).toLocaleDateString('en-US', { month: 'short' })}</span>
</div>
))}
</div>
</div>
</div>
);
}
export default AnalyticsDashboard;Vue Examples
CSV Data Importer
Upload and analyze CSV files interactively.
vue
<!-- src/components/CSVAnalyzer.vue -->
<template>
<div class="csv-analyzer">
<h1>CSV Data Analyzer</h1>
<!-- File Upload -->
<div class="upload-section">
<input
ref="fileInput"
type="file"
accept=".csv"
@change="handleFileUpload"
:disabled="importing"
/>
<div v-if="importing" class="progress">
<div class="spinner"></div>
<p>Importing {{ importProgress }}%</p>
</div>
<div v-if="importError" class="error">
Import failed: {{ importError.message }}
</div>
</div>
<!-- Data Analysis -->
<div v-if="tableName && !importing" class="analysis-section">
<h2>Data Analysis: {{ tableName }}</h2>
<!-- Basic Statistics -->
<div v-if="basicStats" class="stats-grid">
<div class="stat-card">
<h3>Total Rows</h3>
<p>{{ basicStats.total_rows }}</p>
</div>
<div class="stat-card">
<h3>Columns</h3>
<p>{{ basicStats.column_count }}</p>
</div>
</div>
<!-- Column Information -->
<div v-if="columnInfo" class="columns-section">
<h3>Column Information</h3>
<table>
<thead>
<tr>
<th>Column</th>
<th>Type</th>
<th>Non-Null Count</th>
<th>Sample Values</th>
</tr>
</thead>
<tbody>
<tr v-for="col in columnInfo" :key="col.column_name">
<td>{{ col.column_name }}</td>
<td>{{ col.data_type }}</td>
<td>{{ col.non_null_count }}</td>
<td>{{ col.sample_values }}</td>
</tr>
</tbody>
</table>
</div>
<!-- Data Preview -->
<div v-if="previewData" class="preview-section">
<h3>Data Preview (First 10 rows)</h3>
<table>
<thead>
<tr>
<th v-for="header in Object.keys(previewData[0] || {})" :key="header">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in previewData" :key="index">
<td v-for="(value, key) in row" :key="key">
{{ value }}
</td>
</tr>
</tbody>
</table>
</div>
<!-- Quick Queries -->
<div class="quick-queries">
<h3>Quick Analysis</h3>
<button @click="runNumericAnalysis">Numeric Column Analysis</button>
<button @click="runTextAnalysis">Text Column Analysis</button>
<button @click="runMissingDataAnalysis">Missing Data Analysis</button>
</div>
<!-- Analysis Results -->
<div v-if="analysisResults" class="analysis-results">
<h3>Analysis Results</h3>
<pre>{{ JSON.stringify(analysisResults, null, 2) }}</pre>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useImportCSV, useQuery } from '@northprint/duckdb-wasm-adapter-vue';
const fileInput = ref(null);
const tableName = ref('');
const analysisResults = ref(null);
// CSV Import
const {
importCSV,
loading: importing,
error: importError,
progress: importProgress
} = useImportCSV({
onSuccess: (result) => {
tableName.value = result.tableName;
console.log('CSV imported successfully:', result);
}
});
// Basic statistics query
const { data: basicStats } = useQuery(
computed(() => tableName.value ? `
SELECT
COUNT(*) as total_rows,
COUNT(DISTINCT 1) as column_count
FROM ${tableName.value}
` : ''),
undefined,
{ enabled: computed(() => Boolean(tableName.value)) }
);
// Column information
const { data: columnInfo } = useQuery(
computed(() => tableName.value ? `
SELECT
column_name,
data_type,
COUNT(*) as non_null_count,
STRING_AGG(DISTINCT CAST(column_name AS VARCHAR), ', ') as sample_values
FROM information_schema.columns
WHERE table_name = '${tableName.value}'
GROUP BY column_name, data_type
` : ''),
undefined,
{ enabled: computed(() => Boolean(tableName.value)) }
);
// Data preview
const { data: previewData } = useQuery(
computed(() => tableName.value ? `SELECT * FROM ${tableName.value} LIMIT 10` : ''),
undefined,
{ enabled: computed(() => Boolean(tableName.value)) }
);
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (!file) return;
const timestamp = Date.now();
const fileName = `csv_data_${timestamp}`;
await importCSV(file, fileName, {
header: true,
delimiter: ',',
nullString: ''
});
};
const runNumericAnalysis = async () => {
// Implementation for numeric analysis
analysisResults.value = { type: 'numeric', message: 'Numeric analysis would go here' };
};
const runTextAnalysis = async () => {
// Implementation for text analysis
analysisResults.value = { type: 'text', message: 'Text analysis would go here' };
};
const runMissingDataAnalysis = async () => {
// Implementation for missing data analysis
analysisResults.value = { type: 'missing', message: 'Missing data analysis would go here' };
};
</script>
<style scoped>
.csv-analyzer {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin: 2rem 0;
}
.stat-card {
background: #f5f5f5;
padding: 1rem;
border-radius: 8px;
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
th, td {
border: 1px solid #ddd;
padding: 0.5rem;
text-align: left;
}
th {
background-color: #f5f5f5;
}
.progress {
display: flex;
align-items: center;
gap: 1rem;
margin: 1rem 0;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: #d32f2f;
margin: 1rem 0;
}
.quick-queries button {
margin: 0.5rem;
padding: 0.5rem 1rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.analysis-results {
background: #f8f9fa;
padding: 1rem;
border-radius: 4px;
margin: 1rem 0;
}
.analysis-results pre {
overflow-x: auto;
}
</style>Svelte Examples
Real-time Data Monitor
Live updating dashboard with WebSocket integration.
svelte
<!-- src/components/DataMonitor.svelte -->
<script>
import { onMount, onDestroy } from 'svelte';
import { duckdb, mutation } from '@northprint/duckdb-wasm-adapter-svelte';
const db = duckdb({ autoConnect: true });
// Reactive queries
$: metrics = db.query(`
SELECT
COUNT(*) as total_events,
AVG(value) as avg_value,
MAX(timestamp) as last_update
FROM events
WHERE timestamp >= datetime('now', '-1 hour')
`);
$: recentEvents = db.query(`
SELECT * FROM events
ORDER BY timestamp DESC
LIMIT 10
`);
$: eventsByType = db.query(`
SELECT
event_type,
COUNT(*) as count,
AVG(value) as avg_value
FROM events
WHERE timestamp >= datetime('now', '-1 hour')
GROUP BY event_type
ORDER BY count DESC
`);
// Mutation for inserting new events
const insertEvent = mutation({
onSuccess: () => {
// Data will automatically refresh due to reactive queries
console.log('Event inserted');
}
});
// WebSocket connection for real-time data
let socket;
let connected = false;
onMount(() => {
// Initialize table
insertEvent.execute(`
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY,
event_type VARCHAR,
value DECIMAL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
metadata JSON
)
`);
// Connect to WebSocket for real-time data
connectWebSocket();
});
function connectWebSocket() {
socket = new WebSocket('ws://localhost:8080/events');
socket.onopen = () => {
connected = true;
console.log('WebSocket connected');
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
// Insert new event into DuckDB
insertEvent.execute(
'INSERT INTO events (event_type, value, metadata) VALUES (?, ?, ?)',
[data.type, data.value, JSON.stringify(data.metadata || {})]
);
};
socket.onclose = () => {
connected = false;
console.log('WebSocket disconnected');
// Attempt to reconnect
setTimeout(connectWebSocket, 5000);
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
function generateMockEvent() {
const eventTypes = ['page_view', 'click', 'purchase', 'signup'];
const mockEvent = {
type: eventTypes[Math.floor(Math.random() * eventTypes.length)],
value: Math.random() * 100,
metadata: {
user_id: Math.floor(Math.random() * 1000),
page: '/example'
}
};
insertEvent.execute(
'INSERT INTO events (event_type, value, metadata) VALUES (?, ?, ?)',
[mockEvent.type, mockEvent.value, JSON.stringify(mockEvent.metadata)]
);
}
onDestroy(() => {
if (socket) {
socket.close();
}
});
</script>
<div class="data-monitor">
<header>
<h1>Real-time Data Monitor</h1>
<div class="status" class:connected>
Status: {connected ? 'Connected' : 'Disconnected'}
</div>
</header>
<!-- Controls -->
<div class="controls">
<button on:click={generateMockEvent}>Generate Mock Event</button>
<button on:click={() => metrics.refresh()}>Refresh Data</button>
</div>
<!-- Metrics Dashboard -->
<div class="metrics-grid">
{#if $metrics.loading}
<div class="loading">Loading metrics...</div>
{:else if $metrics.data}
<div class="metric-card">
<h3>Total Events (1h)</h3>
<div class="metric-value">{$metrics.data[0]?.total_events || 0}</div>
</div>
<div class="metric-card">
<h3>Average Value</h3>
<div class="metric-value">{($metrics.data[0]?.avg_value || 0).toFixed(2)}</div>
</div>
<div class="metric-card">
<h3>Last Update</h3>
<div class="metric-value">{$metrics.data[0]?.last_update || 'Never'}</div>
</div>
{/if}
</div>
<!-- Events by Type -->
<div class="events-by-type">
<h2>Events by Type (Last Hour)</h2>
{#if $eventsByType.loading}
<div class="loading">Loading...</div>
{:else if $eventsByType.data}
<div class="type-grid">
{#each $eventsByType.data as eventType}
<div class="type-card">
<h4>{eventType.event_type}</h4>
<p>Count: {eventType.count}</p>
<p>Avg Value: {eventType.avg_value.toFixed(2)}</p>
</div>
{/each}
</div>
{/if}
</div>
<!-- Recent Events -->
<div class="recent-events">
<h2>Recent Events</h2>
{#if $recentEvents.loading}
<div class="loading">Loading events...</div>
{:else if $recentEvents.data}
<div class="events-list">
{#each $recentEvents.data as event}
<div class="event-item">
<div class="event-type">{event.event_type}</div>
<div class="event-value">{event.value}</div>
<div class="event-time">{new Date(event.timestamp).toLocaleTimeString()}</div>
</div>
{/each}
</div>
{/if}
</div>
</div>
<style>
.data-monitor {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.status {
padding: 0.5rem 1rem;
border-radius: 4px;
background: #f44336;
color: white;
}
.status.connected {
background: #4caf50;
}
.controls {
margin-bottom: 2rem;
}
.controls button {
margin-right: 1rem;
padding: 0.5rem 1rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.metric-card {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
}
.metric-value {
font-size: 2rem;
font-weight: bold;
color: #007bff;
margin-top: 0.5rem;
}
.type-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.type-card {
background: #e3f2fd;
padding: 1rem;
border-radius: 4px;
}
.events-list {
max-height: 400px;
overflow-y: auto;
}
.event-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
.event-type {
font-weight: bold;
color: #007bff;
}
.event-value {
color: #28a745;
}
.event-time {
color: #6c757d;
font-size: 0.9rem;
}
.loading {
text-align: center;
color: #6c757d;
padding: 2rem;
}
</style>Performance Examples
Query Optimization
javascript
// Bad: Multiple queries
const users = useQuery('SELECT * FROM users');
const departments = useQuery('SELECT * FROM departments');
const projects = useQuery('SELECT * FROM projects');
// Good: Single optimized query
const dashboard = useQuery(`
SELECT
u.id,
u.name,
u.email,
d.name as department_name,
COUNT(p.id) as project_count
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
LEFT JOIN user_projects up ON u.id = up.user_id
LEFT JOIN projects p ON up.project_id = p.id
GROUP BY u.id, u.name, u.email, d.name
ORDER BY u.name
`);Caching Strategy
javascript
// Configure intelligent caching
<DuckDBProvider
autoConnect
config={{
cache: {
enabled: true,
evictionStrategy: 'lru',
maxEntries: 1000,
ttl: 300000, // 5 minutes
memoryLimit: 100 * 1024 * 1024 // 100MB
}
}}
>
<App />
</DuckDBProvider>Large Dataset Handling
javascript
// Efficient pagination for large datasets
function LargeDataTable() {
const [page, setPage] = useState(1);
const pageSize = 100;
const { data, loading } = useQuery(`
SELECT * FROM large_table
ORDER BY id
LIMIT ${pageSize}
OFFSET ${(page - 1) * pageSize}
`);
const { data: countData } = useQuery(
'SELECT COUNT(*) as total FROM large_table'
);
const totalPages = Math.ceil((countData?.[0]?.total || 0) / pageSize);
return (
<div>
{/* Table content */}
<Pagination
current={page}
total={totalPages}
onChange={setPage}
/>
</div>
);
}Integration Examples
With Chart Libraries
javascript
// Chart.js integration
import { Chart as ChartJS } from 'chart.js/auto';
import { Line } from 'react-chartjs-2';
function SalesChart() {
const { data } = useQuery(`
SELECT
DATE(created_at) as date,
SUM(amount) as total_sales
FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAYS)
GROUP BY DATE(created_at)
ORDER BY date
`);
const chartData = {
labels: data?.map(d => d.date) || [],
datasets: [{
label: 'Daily Sales',
data: data?.map(d => d.total_sales) || [],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
};
return <Line data={chartData} />;
}With State Management
javascript
// Redux integration
const salesSlice = createSlice({
name: 'sales',
initialState: { data: [], loading: false },
reducers: {
setSalesData: (state, action) => {
state.data = action.payload;
state.loading = false;
},
setLoading: (state, action) => {
state.loading = action.payload;
}
}
});
function SalesComponent() {
const dispatch = useDispatch();
const { data: salesData } = useQuery(`
SELECT * FROM sales ORDER BY date DESC
`, undefined, {
onSuccess: (data) => {
dispatch(salesSlice.actions.setSalesData(data));
}
});
return <div>{/* Component content */}</div>;
}Testing Examples
Unit Testing
javascript
// Testing React components
import { render, waitFor } from '@testing-library/react';
import { DuckDBProvider } from '@northprint/duckdb-wasm-adapter-react';
import UserList from './UserList';
const TestWrapper = ({ children }) => (
<DuckDBProvider config={{ worker: false }}>
{children}
</DuckDBProvider>
);
test('renders user list', async () => {
render(<UserList />, { wrapper: TestWrapper });
await waitFor(() => {
expect(screen.getByText('Users')).toBeInTheDocument();
});
});Integration Testing
javascript
// E2E testing with Playwright
test('user can import CSV and view data', async ({ page }) => {
await page.goto('/csv-importer');
// Upload CSV file
await page.setInputFiles('input[type="file"]', 'test-data.csv');
// Wait for import to complete
await page.waitForText('Import completed');
// Verify data is displayed
await expect(page.locator('.data-table')).toBeVisible();
await expect(page.locator('.data-table tr')).toHaveCount(6); // 5 rows + header
});These examples demonstrate real-world usage patterns and best practices for building applications with DuckDB WASM Adapter. Each example is designed to be practical and easily adaptable to your specific needs.