Services Layer - cynchro/OldSchoolFrontFrame GitHub Wiki

Services Layer

Services are plain JavaScript functions that own all data-fetching and business logic. They have no access to the DOM and no knowledge of the module that calls them.


Why a services layer?

Separating HTTP calls from module code has practical benefits:

  • Services can be tested in isolation without rendering HTML
  • The same service can be called from multiple modules
  • Module code stays focused on UI state, not API details
  • Service errors are handled explicitly, making failure modes clear

File location

modules/
└── clientes/
    ├── clientes.html
    ├── clientes.js
    └── services/
        └── clientes.service.js   ← your service file

Writing a service

A service file exports plain async functions:

// modules/clientes/services/clientes.service.js

const BASE_URL = '/api/clientes';

export async function getClientes() {
  const res = await fetch(BASE_URL);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

export async function getCliente(id) {
  const res = await fetch(`${BASE_URL}/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

export async function createCliente(data) {
  const res = await fetch(BASE_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

export async function deleteCliente(id) {
  const res = await fetch(`${BASE_URL}/${id}`, { method: 'DELETE' });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
}

Using a service in a module

Import the service explicitly. Call it in mounted() or in methods:

// modules/clientes/clientes.js
import { defineModule } from '../../framework/module/module.js';
import { getClientes, deleteCliente } from './services/clientes.service.js';

defineModule({
  state: () => ({
    clientes: [],
    loading: false,
    error: null,
  }),

  methods: {
    async cargar(ctx) {
      ctx.state.loading = true;
      ctx.state.error = null;
      try {
        ctx.state.clientes = await getClientes();
      } catch (e) {
        ctx.state.error = e.message;
      } finally {
        ctx.state.loading = false;
      }
    },

    async eliminar(ctx, event) {
      const id = Number(event.target.dataset.id);
      await deleteCliente(id);
      ctx.state.clientes = ctx.state.clientes.filter(c => c.id !== id);
    },
  },

  mounted(ctx) {
    ctx.methods.cargar(ctx);
  },
});

Rules for services

Rule Reason
No DOM access in services Services must be UI-agnostic
Return data, throw errors Let the module decide how to display failures
No ctx parameter Services don't know about module state
One file per feature area clientes.service.js, auth.service.js, etc.
Explicit imports — no registry Makes dependencies visible at a glance

Services with auth headers

If your API requires authentication tokens, read them from a shared auth helper:

// components/auth/auth.js
export function getAuthHeaders() {
  const token = localStorage.getItem('token');
  return token ? { Authorization: `Bearer ${token}` } : {};
}
// modules/pedidos/services/pedidos.service.js
import { getAuthHeaders } from '../../../components/auth/auth.js';

export async function getPedidos() {
  const res = await fetch('/api/pedidos', { headers: getAuthHeaders() });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}