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/jsonand 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:
// 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'
);
<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:
const newPost = await Domma.http.post(
'https://jsonplaceholder.typicode.com/posts',
{
title: 'My New Post',
body: 'This is the post content.',
userId: 1
}
);
<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:
const updated = await Domma.http.put(
'https://jsonplaceholder.typicode.com/posts/1',
{
id: 1,
title: 'Updated Title',
body: 'Updated content here.',
userId: 1
}
);
<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:
PUTreplaces the entire resource, whilePATCHonly updates the specified fields. Use PATCH for efficiency when updating single fields.
// 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
}
);
<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:
await Domma.http.delete(
'https://jsonplaceholder.typicode.com/posts/1'
);
// Returns empty object {} on success
<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:
try {
const data = await Domma.http.get(
'https://jsonplaceholder.typicode.com/posts/999'
);
} catch (error) {
console.error('Request failed:', error.message);
// "HTTP Error: 404"
}
<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));