Skip to main content

API Integration

How to communicate with the Yew Search backend API from the frontend.

Backend URL Configuration

The backend URL is configured in src/lib/config.ts:

import { config } from '$lib/config';

// Use in API calls
const url = `${config.BACKEND_URL}/v1/search`;

Default: http://localhost:8443

Authentication

All API calls use cookie-based authentication. Always include credentials: 'include':

const response = await fetch(`${config.BACKEND_URL}/v1/user`, {
method: 'GET',
credentials: 'include', // Required for cookies
headers: { 'Content-Type': 'application/json' },
});

Common Patterns

Simple GET Request

From search.svelte:

const response = await fetch(
`${config.BACKEND_URL}/v1/search?q=${encodeURIComponent(searchQuery)}`,
{
method: 'GET',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
}
);

if (!response.ok) {
throw new Error('Failed to fetch search results');
}

const data = await response.json();

Paginated GET Request

From user-integration.svelte:

const skip = (currentPage - 1) * itemsPerPage;
const response = await fetch(
`${config.BACKEND_URL}/v1/user-integration?skip=${skip}&limit=${itemsPerPage}`,
{
method: 'GET',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
}
);

const data = await response.json();
const items = data.results;
const totalPages = Math.ceil(data.pagination.total / itemsPerPage);

Multiple Parallel API Calls

From user-integration-by-id.svelte:

const [userIntegrationResponse, integrationResponse, contentResponse] = await Promise.all([
fetch(`${config.BACKEND_URL}/v1/user-integration/${id}`, {
method: 'GET',
credentials: 'include',
}),
fetch(`${config.BACKEND_URL}/v1/integration/${domain}`, {
method: 'GET',
credentials: 'include',
}),
fetch(`${config.BACKEND_URL}/v1/user-integration-content?...`, {
method: 'GET',
credentials: 'include',
}),
]);

DELETE Request with Confirmation

From user-integration.svelte:

const response = await fetch(
`${config.BACKEND_URL}/v1/user-integration/${integrationId}`,
{
method: 'DELETE',
credentials: 'include',
}
);

if (response.ok) {
toast.success('Success', {
description: 'Integration removed successfully',
});
// Update UI
} else {
toast.error('Error', {
description: 'Failed to remove integration. Please try again.',
});
}

Error Handling

403 Redirect to Login

if (response.status === 403) {
window.location.href = '/#/login';
return;
}

Try-Catch with Toast

try {
const response = await fetch(`${config.BACKEND_URL}/v1/...`);

if (!response.ok) {
throw new Error('API request failed');
}

const data = await response.json();
// Handle success
} catch (error) {
console.error('Failed to fetch data:', error);
toast.error('Error', {
description: 'Failed to load data. Please try again.',
});
}

TypeScript Interfaces

Define interfaces for API responses:

interface UserIntegration {
id: string;
userId: string;
integrationId: string;
integrationDomain: string;
config: Record<string, any>;
status: 'active' | 'inactive' | 'error';
createdAt: string;
updatedAt: string;
}

interface PaginatedResponse<T> {
results: T[];
pagination: {
skip: number;
limit: number;
total: number;
};
}

Query Parameter Construction

Simple

const query = encodeURIComponent(searchQuery);
const url = `${config.BACKEND_URL}/v1/search?q=${query}`;

Multiple Parameters

const apiUrl = new URL(`${config.BACKEND_URL}/v1/search`);
apiUrl.searchParams.set('q', searchQuery);
apiUrl.searchParams.set('skip', skip.toString());
apiUrl.searchParams.set('limit', limit.toString());

const response = await fetch(apiUrl.toString(), {
method: 'GET',
credentials: 'include',
});

Array Parameters

const domains = ['gmail.com', 'example.com'];
const domainsQuery = domains
.map((d) => `inDomains[]=${encodeURIComponent(d)}`)
.join('&');

const url = `${config.BACKEND_URL}/v1/integration?${domainsQuery}`;

Loading States

let isLoading = $state(false);

async function fetchData() {
isLoading = true;

try {
const response = await fetch(`${config.BACKEND_URL}/v1/...`);
const data = await response.json();
// Handle data
} finally {
isLoading = false; // Always reset loading state
}
}

Best Practices

  1. Always include credentials: 'include' for authenticated requests
  2. Use TypeScript interfaces for all API responses
  3. Handle errors gracefully with try-catch and user feedback
  4. Show loading states during API calls
  5. Encode query parameters with encodeURIComponent()
  6. Reset loading states in finally blocks
  7. Use toast notifications for user feedback on success/error
  8. Redirect to login on 403 responses