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