Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/TheSerchCp/SEAM/llms.txt

Use this file to discover all available pages before exploring further.

ApiClient is a thin but opinionated wrapper around the browser’s native fetch() API. Rather than scattering authentication headers, error handling, and JSON serialization across every repository file, ApiClient centralizes all of that logic in a single request() method. Repositories call the convenience methods (get, post, put, delete) and work purely with resolved data — they never touch raw Response objects or Authorization headers.

Base URL

The API root is defined once in js/config/api.js and imported by both ApiClient and EventBus:
// js/config/api.js
export const API_BASE = 'http://localhost:3001/api/v1';
Every path passed to ApiClient methods is appended to this base, so /users becomes http://localhost:3001/api/v1/users.

Convenience Methods

These four methods are the public API consumed by repository files. Each one delegates to the shared request() method.
MethodSignatureHTTP Verb
getget(path)GET
postpost(path, body)POST
putput(path, body)PUT
deletedelete(path, body)DELETE
// Reading data — no body needed
const users = await ApiClient.get('/users');

// Creating a resource
const newUser = await ApiClient.post('/users', { name: 'Ana', role: 'alumno' });

// Updating a resource
await ApiClient.put(`/users/${id}`, { name: 'Ana García' });

// Deleting a resource
await ApiClient.delete(`/users/${id}`);

request(method, path, body)

All four convenience methods are one-liners that call request(). Understanding request() means understanding every HTTP interaction in the application.
method
'GET' | 'POST' | 'PUT' | 'DELETE'
required
The HTTP verb for the request.
path
string
required
The API path relative to API_BASE, including a leading slash. Supports path parameters inline: '/users/5'.
body
object | null
JSON-serializable request body. Only meaningful for POST and PUT. Defaults to null.

What request() does — step by step

1

Attach Authorization header

Reads session.token and, if present, sets Authorization: Bearer {token} on the request headers. Unauthenticated requests (e.g. /auth/login) go through without a token.
const token = session.token ?? null;
const headers = { 'Content-Type': 'application/json' };
if (token) headers.Authorization = `Bearer ${token}`;
2

Attach X-Socket-ID header

Reads EventBus.socketId and adds the X-Socket-ID header when a socket is active. The server uses this to direct operation:progress events to the correct tab.
const socketId = EventBus.socketId;
if (socketId) headers['X-Socket-ID'] = socketId;
3

Serialize the body

If body is not null, it is serialized with JSON.stringify() and placed in config.body.
if (body !== null) config.body = JSON.stringify(body);
4

Execute the fetch

Calls fetch(API_BASE + path, config) and attempts to parse the response as JSON. A failed JSON parse yields an empty object rather than throwing.
5

Handle HTTP errors

If res.ok is false, an error toast is emitted (for non-GET requests) and an Error is thrown with payload.message as the message.
if (!res.ok) {
    const errorMsg = payload.message ?? `Error ${res.status} en ${method} ${path}`;
    if (method !== 'GET') {
        EventBus.emit('toast:error', { message: errorMsg, duration: 4000 });
    }
    throw new Error(errorMsg);
}
6

Emit success toast and return data

For successful non-GET responses that include a message field, a success toast is emitted automatically. The method returns payload.data when present, otherwise the full parsed body.
if (method !== 'GET' && payload.message) {
    EventBus.emit('toast:success', { message: payload.message, duration: 3000 });
}
return payload.data ?? payload;

Network Errors

If fetch() itself throws (e.g. the server is unreachable), the TypeError is caught and a connection-error toast is emitted before re-throwing:
if (error instanceof TypeError) {
    EventBus.emit('toast:error', {
        message: 'Error de conexión. Por favor, intenta de nuevo.',
        duration: 4000,
    });
}
throw error;

Full request() source

async request(method, path, body = null) {
    const token = session.token ?? null;
    const headers = { 'Content-Type': 'application/json' };
    if (token) headers.Authorization = `Bearer ${token}`;

    const socketId = EventBus.socketId;
    if (socketId) headers['X-Socket-ID'] = socketId;

    const config = { method, headers };
    if (body !== null) config.body = JSON.stringify(body);

    try {
        const res = await fetch(`${API_BASE}${path}`, config);

        let payload;
        try { payload = await res.json(); }
        catch { payload = {}; }

        if (!res.ok) {
            const errorMsg = payload.message ?? `Error ${res.status} en ${method} ${path}`;
            if (method !== 'GET') {
                EventBus.emit('toast:error', { message: errorMsg, duration: 4000 });
            }
            throw new Error(errorMsg);
        }

        if (method !== 'GET' && payload.message) {
            EventBus.emit('toast:success', { message: payload.message, duration: 3000 });
        }

        return payload.data ?? payload;
    } catch (error) {
        if (error instanceof TypeError) {
            EventBus.emit('toast:error', {
                message: 'Error de conexión. Por favor, intenta de nuevo.',
                duration: 4000,
            });
        }
        throw error;
    }
},

The X-Socket-ID Header

The X-Socket-ID header solves a fan-out problem in real-time applications: when a user in one tab creates a resource, the server emits both a data:changed event (broadcast to all tabs) and an operation:progress event (targeted feedback for the initiating tab only). By receiving the socket ID on the request, the server can skip sending operation:progress to other open tabs belonging to the same user — those tabs receive only data:changed and refresh their data silently.
The ID is sourced from EventBus.socketId, which proxies the underlying io().id assigned by the Socket.IO server at connect time.

Repository Usage Examples

Repositories are thin modules that call ApiClient and return the resulting data. They do not handle headers, tokens, or toasts — that is ApiClient’s job.
// js/repositories/users.repository.js (example pattern)
import { ApiClient } from '../core/ApiClient.js';

export const UsersRepository = {
    getAll()          { return ApiClient.get('/users'); },
    getById(id)       { return ApiClient.get(`/users/${id}`); },
    create(payload)   { return ApiClient.post('/users', payload); },
    update(id, patch) { return ApiClient.put(`/users/${id}`, patch); },
    remove(id)        { return ApiClient.delete(`/users/${id}`); },
};
// Inside a page — calling a repository
import { UsersRepository } from '../../repositories/users.repository.js';

const users = await UsersRepository.getAll();
// ApiClient already resolved payload.data, so `users` is the array directly
renderTable(users);
Because ApiClient emits toasts automatically for every non-GET mutation, repositories and pages do not need to add their own success or error notifications for standard CRUD operations. Manual toast:* events should only be emitted for business-logic conditions that ApiClient cannot infer from the HTTP response alone.

Build docs developers (and LLMs) love