HTTP Client

Fetch-based HTTP client for making API requests with Promises and async/await support.

Overview

Domma.http provides a simple, Promise-based HTTP client built on the Fetch API. All methods return Promises that resolve to parsed JSON responses.

Note: All requests automatically set Content-Type: application/json and parse responses as JSON.

API Reference

Method Signature Description
GET http.get(url, config) Fetch data from a URL
POST http.post(url, data, config) Send data to create a resource
PUT http.put(url, data, config) Send data to fully replace a resource
PATCH http.patch(url, data, config) Send data to partially update a resource
DELETE http.delete(url, config) Delete a resource

Config Options

The config parameter accepts any valid fetch options:

{
    headers: {
        'Authorization': 'Bearer token123',
        'X-Custom-Header': 'value'
    },
    mode: 'cors',
    credentials: 'include'
}

The live demos use example records. POST, PUT, PATCH and DELETE responses are simulated.

GET Fetch Data

Fetch posts from the example API:

GET Fetch Data
// Fetch a single post
            const post = await Domma.http.get(
            'https://jsonplaceholder.typicode.com/posts/1'
            );
            console.log(post.title);

            // Fetch multiple posts
            const posts = await Domma.http.get(
            'https://jsonplaceholder.typicode.com/posts?_limit=3'
            );
        
Click "Fetch Posts" to see the response...
<div class="http-demo-grid"><div>
        <pre class="code-block language-javascript mb-4">// Fetch a single post
            const post = await Domma.http.get(
            'https://jsonplaceholder.typicode.com/posts/1'
            );
            console.log(post.title);

            // Fetch multiple posts
            const posts = await Domma.http.get(
            'https://jsonplaceholder.typicode.com/posts?_limit=3'
            );
        </pre>
        <button type="button" class="btn btn-primary" id="demo-get">
            <span data-icon="download" data-icon-size="16"></span>
            Fetch Posts
        </button>
    </div>
    <div>
        <label class="form-label">Response:</label>
        <div class="http-response-area" id="get-response">Click "Fetch Posts" to see the response...</div>
    </div>
</div>
/**
 * GET Fetch Data - from public/showcase/http.
 */
export function mount(root) {
      // Helper to format JSON
    function formatJson(obj) {
        return JSON.stringify(obj, null, 2);
      }
      // GET Demo
    $(root).find('#demo-get').on('click', async () => {
        const output = $(root).find('#get-response');
        output.addClass('loading').text('Loading...');

        try {
          const posts = await Domma.http.get('/api/plugins/domma-showcase/examples/posts?_limit=3');
          output.removeClass('loading error').text(formatJson(posts));
        } catch (err) {
          output.removeClass('loading').addClass('error').text('Error: ' + err.message);
        }
      });
}

POST Create Resource

Create a new post using example data:

POST Create Resource
const newPost = await Domma.http.post(
            'https://jsonplaceholder.typicode.com/posts',
            {
            title: 'My New Post',
            body: 'This is the post content.',
            userId: 1
            }
            );
        
Click "Create Post" to see the response...
<div class="http-demo-grid"><div>
        <div class="form-group mb-3">
            <label class="form-label">Title</label>
            <input type="text" class="form-input" id="post-title" value="My New Post">
        </div>
        <div class="form-group mb-3">
            <label class="form-label">Body</label>
            <textarea class="form-textarea" id="post-body" rows="2">This is the post content.</textarea>
        </div>
        <pre class="code-block language-javascript mb-4">const newPost = await Domma.http.post(
            'https://jsonplaceholder.typicode.com/posts',
            {
            title: 'My New Post',
            body: 'This is the post content.',
            userId: 1
            }
            );
        </pre>
        <button type="button" class="btn btn-primary" id="demo-post">
            <span data-icon="plus" data-icon-size="16"></span>
            Create Post
        </button>
    </div>
    <div>
        <label class="form-label">Response:</label>
        <div class="http-response-area" id="post-response">Click "Create Post" to see the response...</div>
    </div>
</div>
/**
 * POST Create Resource - from public/showcase/http.
 */
export function mount(root) {
      // Helper to format JSON
    function formatJson(obj) {
        return JSON.stringify(obj, null, 2);
      }
      // POST Demo
    $(root).find('#demo-post').on('click', async () => {
        const output = $(root).find('#post-response');
        const title = $(root).find('#post-title').val();
        const body = $(root).find('#post-body').val();

        output.addClass('loading').text('Creating...');

        try {
          const result = await Domma.http.post('/api/plugins/domma-showcase/examples/posts', {
            title: title,
            body: body,
            userId: 1
          });
          output.removeClass('loading error').text(formatJson(result));
          E.toast('Post created successfully!', {type: 'success'});
        } catch (err) {
          output.removeClass('loading').addClass('error').text('Error: ' + err.message);
        }
      });
}

PUT Update Resource

Update an existing post:

PUT Update Resource
const updated = await Domma.http.put(
            'https://jsonplaceholder.typicode.com/posts/1',
            {
            id: 1,
            title: 'Updated Title',
            body: 'Updated content here.',
            userId: 1
            }
            );
        
Click "Update Post" to see the response...
<div class="http-demo-grid"><div>
        <pre class="code-block language-javascript mb-4">const updated = await Domma.http.put(
            'https://jsonplaceholder.typicode.com/posts/1',
            {
            id: 1,
            title: 'Updated Title',
            body: 'Updated content here.',
            userId: 1
            }
            );
        </pre>
        <button type="button" class="btn btn-warning" id="demo-put">
            <span data-icon="edit" data-icon-size="16"></span>
            Update Post
        </button>
    </div>
    <div>
        <label class="form-label">Response:</label>
        <div class="http-response-area" id="put-response">Click "Update Post" to see the response...</div>
    </div>
</div>
/**
 * PUT Update Resource - from public/showcase/http.
 */
export function mount(root) {
      // Helper to format JSON
    function formatJson(obj) {
        return JSON.stringify(obj, null, 2);
      }
      // PUT Demo
    $(root).find('#demo-put').on('click', async () => {
        const output = $(root).find('#put-response');
        output.addClass('loading').text('Updating...');

        try {
          const result = await Domma.http.put('/api/plugins/domma-showcase/examples/posts/1', {
            id: 1,
            title: 'Updated Title',
            body: 'This post has been updated.',
            userId: 1
          });
          output.removeClass('loading error').text(formatJson(result));
          E.toast('Post updated successfully!', {type: 'success'});
        } catch (err) {
          output.removeClass('loading').addClass('error').text('Error: ' + err.message);
        }
      });
}

PATCH Partial Update

Update only specific fields without replacing the entire resource:

PUT vs PATCH: PUT replaces the entire resource, while PATCH only updates the specified fields. Use PATCH for efficiency when updating single fields.

PATCH Partial Update
// Only update the title field
            const patched = await Domma.http.patch(
            'https://jsonplaceholder.typicode.com/posts/1',
            {
            title: 'Patched Title'
            // Other fields (body, userId) remain unchanged
            }
            );
        
Click "Patch Post" to see the response...
<div class="http-demo-grid"><div>
        <div class="form-group mb-3">
            <label class="form-label">Update Title Only</label>
            <input type="text" class="form-input" id="patch-title" value="Patched Title">
        </div>
        <pre class="code-block language-javascript mb-4">// Only update the title field
            const patched = await Domma.http.patch(
            'https://jsonplaceholder.typicode.com/posts/1',
            {
            title: 'Patched Title'
            // Other fields (body, userId) remain unchanged
            }
            );
        </pre>
        <button type="button" class="btn btn-info" id="demo-patch">
            <span data-icon="edit" data-icon-size="16"></span>
            Patch Post
        </button>
    </div>
    <div>
        <label class="form-label">Response:</label>
        <div class="http-response-area" id="patch-response">Click "Patch Post" to see the response...</div>
    </div>
</div>
/**
 * PATCH Partial Update - from public/showcase/http.
 */
export function mount(root) {
      // Helper to format JSON
    function formatJson(obj) {
        return JSON.stringify(obj, null, 2);
      }
      // PATCH Demo
    $(root).find('#demo-patch').on('click', async () => {
        const output = $(root).find('#patch-response');
        const title = $(root).find('#patch-title').val();
        output.addClass('loading').text('Patching...');

        try {
          const result = await Domma.http.patch('/api/plugins/domma-showcase/examples/posts/1', {
            title: title
          });
          output.removeClass('loading error').text(formatJson(result));
          E.toast('Post patched successfully! Only title field was updated.', {type: 'success'});
        } catch (err) {
          output.removeClass('loading').addClass('error').text('Error: ' + err.message);
        }
      });
}

DELETE Delete Resource

Delete a post:

DELETE Delete Resource
await Domma.http.delete(
            'https://jsonplaceholder.typicode.com/posts/1'
            );
            // Returns empty object {} on success
        
Click "Delete Post" to see the response...
<div class="http-demo-grid"><div>
        <pre class="code-block language-javascript mb-4">await Domma.http.delete(
            'https://jsonplaceholder.typicode.com/posts/1'
            );
            // Returns empty object {} on success
        </pre>
        <button type="button" class="btn btn-danger" id="demo-delete">
            <span data-icon="trash" data-icon-size="16"></span>
            Delete Post
        </button>
    </div>
    <div>
        <label class="form-label">Response:</label>
        <div class="http-response-area" id="delete-response">Click "Delete Post" to see the response...</div>
    </div>
</div>
/**
 * DELETE Delete Resource - from public/showcase/http.
 */
export function mount(root) {
      // Helper to format JSON
    function formatJson(obj) {
        return JSON.stringify(obj, null, 2);
      }
      // DELETE Demo
    $(root).find('#demo-delete').on('click', async () => {
        const output = $(root).find('#delete-response');
        output.addClass('loading').text('Deleting...');

        try {
          const result = await Domma.http.delete('/api/plugins/domma-showcase/examples/posts/1');
          output.removeClass('loading error').text(formatJson(result) + '\n\n// Post deleted successfully');
          E.toast('Post deleted successfully!', {type: 'success'});
        } catch (err) {
          output.removeClass('loading').addClass('error').text('Error: ' + err.message);
        }
      });
}

Error Handling

Handle errors with try/catch:

Error Handling
try {
            const data = await Domma.http.get(
            'https://jsonplaceholder.typicode.com/posts/999'
            );
            } catch (error) {
            console.error('Request failed:', error.message);
            // "HTTP Error: 404"
            }
        
Click to trigger an error...
<div class="http-demo-grid"><div>
        <pre class="code-block language-javascript mb-4">try {
            const data = await Domma.http.get(
            'https://jsonplaceholder.typicode.com/posts/999'
            );
            } catch (error) {
            console.error('Request failed:', error.message);
            // "HTTP Error: 404"
            }
        </pre>
        <button type="button" class="btn btn-outline" id="demo-error">
            <span data-icon="alert-triangle" data-icon-size="16"></span>
            Trigger 404 Error
        </button>
    </div>
    <div>
        <label class="form-label">Response:</label>
        <div class="http-response-area" id="error-response">Click to trigger an error...</div>
    </div>
</div>
/**
 * Error Handling - from public/showcase/http.
 */
export function mount(root) {
      // Error Demo
    $(root).find('#demo-error').on('click', async () => {
        const output = $(root).find('#error-response');
        output.addClass('loading').text('Requesting invalid resource...');

        try {
          await Domma.http.get('/api/plugins/domma-showcase/examples/posts/99999');
          output.removeClass('loading').text('Unexpected success');
        } catch (err) {
          output.removeClass('loading').addClass('error').text('Caught error:\n' + err.message);
          E.toast('Request failed: ' + err.message, {type: 'error'});
        }
      });
}

Custom Headers

Add custom headers to your requests:

// Add authorization header
const data = await Domma.http.get('/api/protected', {
    headers: {
        'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...',
        'X-Request-ID': 'abc123'
    }
});

// Custom content type
const xml = await Domma.http.post('/api/xml', xmlData, {
    headers: {
        'Content-Type': 'application/xml'
    }
});

Service Layer & Configuration Patterns

Organize API requests using service layer classes and centralized configuration for better maintainability and testability.

API Service Class

// Create a service class for your API
class UserService {
    constructor(baseURL = '/api') {
        this.baseURL = baseURL;
    }

    async getAll() {
        return Domma.http.get(`${this.baseURL}/users`);
    }

    async getById(id) {
        return Domma.http.get(`${this.baseURL}/users/${id}`);
    }

    async create(userData) {
        return Domma.http.post(`${this.baseURL}/users`, userData);
    }

    async update(id, userData) {
        return Domma.http.put(`${this.baseURL}/users/${id}`, userData);
    }

    async delete(id) {
        return Domma.http.delete(`${this.baseURL}/users/${id}`);
    }

    async search(query) {
        return Domma.http.get(`${this.baseURL}/users/search?q=${encodeURIComponent(query)}`);
    }
}

// Usage
const userService = new UserService();
const users = await userService.getAll();
const user = await userService.getById(123);

Centralized API Configuration

// Define all API endpoints in config
const apiConfig = {
    baseURL: 'https://api.example.com',
    endpoints: {
        users: {
            list: '/users',
            get: '/users/:id',
            create: '/users',
            update: '/users/:id',
            delete: '/users/:id'
        },
        posts: {
            list: '/posts',
            get: '/posts/:id',
            byUser: '/users/:userId/posts'
        }
    },
    headers: {
        'Authorization': 'Bearer token123',
        'X-App-Version': '1.0.0'
    }
};

// Generic API client
class APIClient {
    constructor(config) {
        this.config = config;
    }

    buildURL(endpoint, params = {}) {
        let url = this.config.baseURL + endpoint;
        // Replace :param placeholders
        Object.entries(params).forEach(([key, value]) => {
            url = url.replace(`:${key}`, value);
        });
        return url;
    }

    async request(method, endpoint, data = null, params = {}) {
        const url = this.buildURL(endpoint, params);
        const options = { headers: this.config.headers };

        if (method === 'GET') {
            return Domma.http.get(url, options);
        } else if (method === 'POST') {
            return Domma.http.post(url, data, options);
        } else if (method === 'PUT') {
            return Domma.http.put(url, data, options);
        } else if (method === 'DELETE') {
            return Domma.http.delete(url, options);
        }
    }
}

// Usage
const api = new APIClient(apiConfig);

// Get user by ID
const user = await api.request('GET', apiConfig.endpoints.users.get, null, { id: 123 });

// Create new user
const newUser = await api.request('POST', apiConfig.endpoints.users.create, {
    name: 'Alice',
    email: 'alice@example.com'
});

Using $.setup() for API Interactions

// Declaratively handle API-driven UI interactions
$.setup({
    '#user-list': {
        events: {
            ready: async (e, $el) => {
                // Load users when component ready
                const users = await userService.getAll();
                const html = users.map(u => `
                    
                        ${u.name}
                        ${u.email}
                        Edit
                        Delete
                    
                `).join('');
                $el.html(html);
            }
        }
    },

    '#user-form': {
        events: {
            submit: async (e, $el) => {
                e.preventDefault();

                const formData = {
                    name: $el.find('[name="name"]').val(),
                    email: $el.find('[name="email"]').val()
                };

                try {
                    const user = await userService.create(formData);
                    Domma.elements.toast.success('User created!');
                    $('#user-list').trigger('reload');
                } catch (err) {
                    Domma.elements.toast.error('Failed: ' + err.message);
                }
            }
        }
    },

    '#search-users': {
        events: {
            input: _.debounce(async (e, $el) => {
                const query = $el.val();
                if (query.length < 3) return;

                const results = await userService.search(query);
                $('#search-results').html(
                    results.map(u => `${u.name}`).join('')
                );
            }, 300)
        }
    }
});

Request/Response Interceptor Pattern

// Wrap Domma.http with interceptors
class HTTPClient {
    constructor() {
        this.requestInterceptors = [];
        this.responseInterceptors = [];
        this.errorInterceptors = [];
    }

    addRequestInterceptor(fn) {
        this.requestInterceptors.push(fn);
    }

    addResponseInterceptor(fn) {
        this.responseInterceptors.push(fn);
    }

    addErrorInterceptor(fn) {
        this.errorInterceptors.push(fn);
    }

    async request(method, url, data = null, config = {}) {
        // Run request interceptors
        for (const interceptor of this.requestInterceptors) {
            config = await interceptor(config);
        }

        try {
            // Make request
            let response;
            if (method === 'GET') {
                response = await Domma.http.get(url, config);
            } else if (method === 'POST') {
                response = await Domma.http.post(url, data, config);
            } else if (method === 'PUT') {
                response = await Domma.http.put(url, data, config);
            } else if (method === 'DELETE') {
                response = await Domma.http.delete(url, config);
            }

            // Run response interceptors
            for (const interceptor of this.responseInterceptors) {
                response = await interceptor(response);
            }

            return response;
        } catch (error) {
            // Run error interceptors
            for (const interceptor of this.errorInterceptors) {
                error = await interceptor(error);
            }
            throw error;
        }
    }
}

// Usage
const client = new HTTPClient();

// Add auth header to all requests
client.addRequestInterceptor(async (config) => {
    const token = localStorage.getItem('auth_token');
    if (token) {
        config.headers = config.headers || {};
        config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
});

// Log all responses
client.addResponseInterceptor(async (response) => {
    console.log('API Response:', response);
    return response;
});

// Handle 401 errors globally
client.addErrorInterceptor(async (error) => {
    if (error.status === 401) {
        // Redirect to login
        window.location = '/login';
    }
    return error;
});

Benefits of Service Layer Pattern

  • Centralization - All API logic in dedicated service classes
  • Reusability - Share endpoints across components
  • Testability - Easy to mock services for unit tests
  • Type Safety - Define interfaces for API responses
  • Maintainability - Change endpoint URLs in one place
  • Consistency - Uniform error handling and headers

Scattered Requests vs Service Layer

Scattered (Hard to Maintain)

// Component 1
const users = await Domma.http.get(
    'https://api.example.com/users'
);

// Component 2 - different file
const user = await Domma.http.get(
    'https://api.example.com/users/' + id
);

// Component 3 - another file
await Domma.http.post(
    'https://api.example.com/users',
    userData
);

// URL repeated 3 times
// Hard to change base URL
// No consistent error handling

Service Layer (Centralized)

// services/user.js
class UserService {
    baseURL = 'https://api.example.com';

    getAll() {
        return Domma.http.get(`${this.baseURL}/users`);
    }

    getById(id) {
        return Domma.http.get(`${this.baseURL}/users/${id}`);
    }

    create(data) {
        return Domma.http.post(`${this.baseURL}/users`, data);
    }
}

// Usage anywhere
const userService = new UserService();
const users = await userService.getAll();
const user = await userService.getById(id);
await userService.create(userData);

Quick Reference

Basic Usage

// GET
const users = await Domma.http.get('/api/users');

// POST
const user = await Domma.http.post('/api/users', {
    name: 'John',
    email: 'john@example.com'
});

// PUT
await Domma.http.put('/api/users/1', { name: 'Jane' });

// DELETE
await Domma.http.delete('/api/users/1');

With async/await

async function fetchData() {
    try {
        const data = await Domma.http.get('/api/data');
        console.log(data);
    } catch (err) {
        console.error(err.message);
    }
}

// Or with .then()
Domma.http.get('/api/data')
    .then(data => console.log(data))
    .catch(err => console.error(err));