Frontend Client Integration Guide
Everything you or your AI coding assistant needs to connect a web application (React, Next.js, Vue, Svelte, or plain HTML/JS) to VibeBackr.
1. Overview & Data Model
VibeBackr serves as the instant cloud backend for any web application without server setup:
- Base URL:
https://vibebac.kr(orhttp://localhost:8080for local development) - Collections: Logical namespaces for records (e.g.
"notes","projects","todos"). - Universal Records: Every record has an ID, a collection ID, a
title, text/markdowncontent, and a flexible JSONdatafield for arbitrary structured payloads. - 5-Tier Recursive Permissions: Permissions are automatically calculated in database queries: Owner → Manager Hierarchy → Public Domain → Email Shares → Admin.
2. Authentication for Frontends
Every authenticated API request requires an Authorization: Bearer <token> header.
Frontend User Authentication (Google SSO)
Redirect the user to the login endpoint passing your frontend origin as redirect_uri:
// 1. Redirect to Google login:
const loginUrl = 'https://vibebac.kr/auth/login/google?redirect_uri=' + encodeURIComponent(window.location.origin);
window.location.href = loginUrl;
// 2. On return, extract the token from the URL hash:
if (window.location.hash.includes('access_token=')) {
const params = new URLSearchParams(window.location.hash.substring(1));
const token = params.get('access_token');
if (token) {
localStorage.setItem('vb_token', token);
window.location.hash = ''; // Clean up URL
}
}
Making Authenticated Requests
const token = localStorage.getItem('vb_token');
const res = await fetch('https://vibebac.kr/api/collections', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const collections = await res.json();
3. Core REST Endpoints
Collections
GET /api/collections— Returns list of accessible collectionsCollection[]POST /api/collections— Creates a new collection ({"name": "todos", "default_visibility": "private"})
Universal Records CRUD
GET /api/collections/{collection_id}/records— Lists records visible to current user.POST /api/collections/{collection_id}/records— Creates a new record.GET /api/collections/{collection_id}/records/{record_id}— Fetches single record.PUT /api/collections/{collection_id}/records/{record_id}— Updates title, content, or custom data.DELETE /api/collections/{collection_id}/records/{record_id}— Deletes record.
Creating a Record:
const res = await fetch('https://vibebac.kr/api/collections/col_123/records', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Weekly Standup Notes',
content: 'Discussed sprint roadmap and migration.',
data: JSON.stringify({ priority: 'high', tags: ['eng', 'q4'] }),
access_policy: {
visibility: 'shared',
shares: ['colleague@example.com']
}
}),
});
const record = await res.json();
Search & RAG
VibeBackr includes 768-dimensional vector semantic search (EmbeddingGemma 2 on Neon Postgres) with automatic text fallback:
const searchRes = await fetch('https://vibebac.kr/api/collections/col_123/search', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'sprint goals and migration',
limit: 10
}),
});
const { results } = await searchRes.json();
// Returns array of { record: Record, score: number }
4. Drop-in TypeScript Client
Copy and paste this helper directly into your frontend codebase (e.g. src/lib/vibeBackr.ts):
export interface VibeRecord> {
id: string;
collection_id: string;
title: string;
content: string;
data: string; // JSON payload; parse with JSON.parse()
created_by: string;
created_at: string;
updated_at: string;
access_policy: {
visibility: 'private' | 'public' | 'shared';
shares?: string[];
};
}
export function createVibeClient(baseUrl = 'https://vibebac.kr') {
const getToken = () => (typeof window !== 'undefined' ? localStorage.getItem('vb_token') : null);
const setToken = (t: string) => (typeof window !== 'undefined' ? localStorage.setItem('vb_token', t) : null);
async function api(path: string, options: RequestInit = {}) {
const token = getToken();
const headers: Record = {
'Content-Type': 'application/json',
...(options.headers as Record),
};
if (token) headers['Authorization'] = `Bearer ${token}`;
const res = await fetch(`${baseUrl}${path}`, { ...options, headers });
if (!res.ok) {
const err = await res.json().catch(() => ({ error: res.statusText }));
throw new Error(err.error || `HTTP ${res.status}`);
}
return res.json();
}
return {
getToken,
setToken,
getLoginUrl(provider = 'google', redirectUri = window.location.origin) {
return `${baseUrl}/auth/login/${provider}?redirect_uri=${encodeURIComponent(redirectUri)}`;
},
handleAuthCallback() {
if (typeof window === 'undefined') return null;
if (window.location.hash.includes('access_token=')) {
const params = new URLSearchParams(window.location.hash.substring(1));
const token = params.get('access_token');
if (token) {
setToken(token);
window.location.hash = '';
return token;
}
}
return getToken();
},
listCollections: () => api('/api/collections'),
listRecords: (colId: string) => api(`/api/collections/${colId}/records`),
getRecord: (colId: string, recId: string) => api(`/api/collections/${colId}/records/${recId}`),
createRecord: (colId: string, payload: { title: string; content?: string; data?: any; access_policy?: any }) =>
api(`/api/collections/${colId}/records`, {
method: 'POST',
body: JSON.stringify({
...payload,
data: typeof payload.data === 'string' ? payload.data : JSON.stringify(payload.data || {}),
}),
}),
updateRecord: (colId: string, recId: string, payload: any) =>
api(`/api/collections/${colId}/records/${recId}`, {
method: 'PUT',
body: JSON.stringify({
...payload,
data: typeof payload.data === 'string' ? payload.data : JSON.stringify(payload.data || {}),
}),
}),
deleteRecord: (colId: string, recId: string) =>
api(`/api/collections/${colId}/records/${recId}`, { method: 'DELETE' }),
search: (colId: string, query: string, limit = 20) =>
api(`/api/collections/${colId}/search`, {
method: 'POST',
body: JSON.stringify({ query, limit }),
}),
};
}