A modular, ultra-lightweight, and reactive HTTP client built for the modern web ecosystem. While it serves as the core networking module for BlackbirdJS, it is architected to be completely modular and framework-agnostic. With zero external dependencies, it works out-of-the-box as a plug-and-play state machine in React, Vue, Svelte, or Vanilla JavaScript applications.
Built on native web specifications (fetch, EventTarget, and AbortController), it instantly elevates standard raw network promises into observable, event-driven state streams.
- 100% Plug-and-Play: Completely decoupled. Use it inside any UI framework or Node.js environment without pulling in any extra dependencies.
- Reactive State Machine: Track
loading,data, anderrorstates instantly using a universal.onChange()subscriber pattern. - Advanced Request Interceptors: Safely chain sequential request middleware to inject context or merge nested headers deeply without erasing defaults.
- Native Request Cancellation: Cancel running network operations cleanly using an integrated
.abort()handle. - Intelligent URL Resolution: Dynamically manages absolute vs. relative paths and enforces or strips trailing slashes seamlessly.
- Safe Type Serialization: Automated JSON string processing that safely identifies and leaves multi-part
FormDataor binaryBlobdata intact.
npm install @blackbirdjs/httpBy default, the package exports a pre-configured global singleton instance (http) ready for immediate use.
import http from '@blackbirdjs/http';
const response = http.get('/api/users');
// The request returns an observable state target immediately
response.onChange((state) => {
if (state.loading) {
console.log('Fetching user data...');
} else if (state.error) {
console.error('An error occurred:', state.error);
} else {
console.log('Data retrieved successfully:', state.data);
}
});import http from '@blackbirdjs/http';
http.post('/api/users', { name: 'John Doe' })
.onChange((state) => {
if (!state.loading && !state.error) {
console.log('User created:', state.data);
}
});You can import the raw BlackbirdHTTP class to spin up separate client configurations for different servers or API contexts.
import { BlackbirdHTTP } from '@blackbirdjs/http';
const githubClient = new BlackbirdHTTP({
baseURL: 'https://github.com',
trailingSlash: false,
headers: {
'Accept': 'application/vnd.github+json',
'Content-Type': 'application/json'
}
});
githubClient.get('/users/blackbirdjs');Interceptors let you tap into requests and modify paths or deep configuration settings before the native network request hits the browser runtime.
import http from '@blackbirdjs/http';
// Automatically inject an authorization token securely into every request
http.interceptors.request.push((url, options) => {
const token = localStorage.getItem('token');
return {
url,
options: {
headers: {
'Authorization': `Bearer ${token}`
}
}
};
});You can kill long-running requests instantly. This is highly useful for clearing memory leaks or component cleanup workflows in UI frameworks.
import http from '@blackbirdjs/http';
const request = http.get('/api/heavy-data-stream');
// Sometime later, if the user navigates away:
request.abort();
request.onChange((state) => {
if (state.error) {
console.log(state.error); // Output: "Request cancelled by user."
}
});When instantiating new BlackbirdHTTP(config), the following optional properties are supported:
| Property | Type | Default | Description |
|---|---|---|---|
baseURL |
string |
'' |
Prepend a baseline target URL structure to relative routing endpoints. |
trailingSlash |
boolean |
false |
true enforces a trailing / layout on incoming path names; false safely strips it. |
headers |
object |
{ 'Content-Type': 'application/json' } |
Global fallback request headers layer. |
Because it extends native browser events, hooking it into a raw UI component is incredibly straightforward:
import http from '@blackbirdjs/http';
const container = document.getElementById('user-list');
http.get('/api/users').onChange((state) => {
if (state.loading) {
container.innerHTML = '<p>Loading users...</p>';
return;
}
if (state.error) {
container.innerHTML = `<p class="error">${state.error}</p>`;
return;
}
container.innerHTML = state.data.map(user => `<li>${user.name}</li>`).join('');
});Licensed under the Apache-2.0 License.