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 (or http://localhost:8080 for 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/markdown content, and a flexible JSON data field 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 collections Collection[]
  • 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 }),
      }),
  };
}