A small, dependency-free, promise-based wrapper around XMLHttpRequest with a fluent (chainable) API.
- Fluent builder API:
req.init().withEndpoint(...).withHeader(...).post() - Promise-based, works with
async/await - Automatic JSON serialization and parsing
- Request lifecycle callbacks (
onOpened,onHeadersReceived,onLoading,onDone) - Upload progress, timeout, cancellation (
AbortSignal), credentials andresponseTypesupport
npm i @rightek/req -Simport req from '@rightek/req';
req.init()
.withEndpoint('https://jsonplaceholder.typicode.com/todos')
.withData(1)
.get()
.then(({ response, headers, status }) => {
console.log(response);
})
.catch(e => {
console.error(e);
});Or with async/await:
const { response } = await req.init()
.withEndpoint('https://jsonplaceholder.typicode.com/todos')
.withData(1)
.get();Note: Call
req.init()for every request. It returns a fresh builder, so headers, data and callbacks are never shared between requests.
// GET https://api.example.com/users/42
await req.init()
.withEndpoint('https://api.example.com/users')
.withData(42)
.get();When withData receives an object, it is converted to a query string. null and undefined values are skipped, and arrays are repeated as key=a&key=b.
// GET https://api.example.com/users?page=2&tags=a&tags=b
await req.init()
.withEndpoint('https://api.example.com/users')
.withData({ page: 2, tags: ['a', 'b'] })
.get();The body is serialized with JSON.stringify and Content-Type: application/json is added automatically (unless you already set a Content-Type header).
const { response } = await req.init()
.withEndpoint('https://api.example.com/users')
.withData({ username: 'john' })
.withHeader('Authorization', 'Bearer YOUR_TOKEN')
.post();upload() sends the data as-is (no JSON serialization), so it is suitable for FormData, Blob, etc.
const form = new FormData();
form.append('file', fileInput.files[0]);
await req.init()
.withEndpoint('https://api.example.com/upload')
.withData(form)
.onUploadProgress(e => {
if (e.lengthComputable) console.log(`${Math.round((e.loaded / e.total) * 100)}%`);
})
.upload();const controller = new AbortController();
const request = req.init()
.withEndpoint('https://api.example.com/slow')
.get({ timeout: 5000, signal: controller.signal });
// later...
controller.abort();
try {
await request;
} catch (e) {
console.log(e.type); // 'timeout' | 'abort' | ...
}const { response: blob } = await req.init()
.withEndpoint('https://api.example.com/report.pdf')
.get({ responseType: 'blob' });req.init()
.withEndpoint('https://api.example.com/users')
.onOpened(() => console.log('opened'))
.onHeadersReceived(() => console.log('headers received'))
.onLoading(() => console.log('loading'))
.onDone(() => console.log('done'))
.get();Returns a new request builder. Must be called before any of the methods below.
All with... and on... methods return the builder, so they can be chained.
| Method | Description |
|---|---|
withEndpoint(endpoint) |
Sets the request URL. Throws an Error synchronously if the URL is not valid. |
withData(data) |
Sets the request data (see How data is handled). |
withHeader(key, value) |
Adds a single request header. |
withHeaders(headers) |
Replaces all headers. Expects an array of { key, value } objects. |
onOpened(func) |
Called when the request is opened. |
onHeadersReceived(func) |
Called when response headers are received. |
onLoading(func) |
Called while the response body is being received. |
onDone(func) |
Called when the request completes (on success and on failure). |
onUploadProgress(func) |
Receives the ProgressEvent of the upload. |
All request methods return a Promise. If no valid endpoint has been set, the promise is rejected with an Error('Url is not valid.').
| Method | Description |
|---|---|
get(options) |
Sends a GET request. withData is appended as a path segment or query string. |
post(options) |
Sends a POST request. Objects are JSON-serialized. |
upload(options) |
Sends a POST request with the data as-is (for FormData, Blob, ...). |
send(method, options) |
Sends a request with any method. The data is sent as-is. |
The METHOD constant is also exported:
import req, { METHOD } from '@rightek/req';
req.init()
.withEndpoint('https://api.example.com/users/42')
.send(METHOD.DELETE);METHOD contains GET, POST, PUT and DELETE.
Every request method accepts an optional options object:
| Option | Type | Default | Description |
|---|---|---|---|
verbose |
boolean |
false |
Logs failed requests to the console. |
timeout |
number |
0 |
Timeout in milliseconds. 0 means no timeout. |
withCredentials |
boolean |
false |
Sends cookies and auth headers on cross-site requests. |
responseType |
string |
'' |
XMLHttpRequest.responseType ('', 'text', 'json', 'blob', 'arraybuffer', ...). |
signal |
AbortSignal |
null |
Aborts the request when the signal is aborted. |
| Method | withData value |
Behavior |
|---|---|---|
get |
number / string | Appended to the URL as a path segment (/todos + 1 -> /todos/1). |
get |
object | Converted to a query string. |
post |
object / array / number / boolean | Serialized with JSON.stringify; Content-Type: application/json is added if missing. |
post |
string | Treated as an already-serialized JSON string and sent as-is (not stringified twice). |
post |
FormData, Blob, ArrayBuffer, URLSearchParams |
Sent as-is; the browser sets the Content-Type. |
upload / send |
anything | Sent as-is. |
On success (any 2xx status), the promise resolves with:
{
response, // parsed JSON (object/array), or the raw response
headers, // response headers as an object, with lower-cased names
status // HTTP status code
}The response body is parsed as JSON when the Content-Type contains json, or when the body is a JSON object/array. Otherwise the raw response is returned.
On failure, the promise is rejected with an object:
{
type, // 'http' | 'network' | 'timeout' | 'abort'
status, // HTTP status code (0 when no response was received)
statusText,
responseText, // undefined when responseType is not text
headers // response headers as an object
}type |
Meaning |
|---|---|
http |
The server responded with a non-2xx status. |
network |
The request could not be made (offline, CORS failure, DNS error, ...). |
timeout |
The timeout option was exceeded. |
abort |
The request was aborted via signal. |
Errors thrown while preparing the request (for example an invalid header name) reject the promise with the original Error.
Works in all browsers that support XMLHttpRequest, Promise and URL. The library ships modern JavaScript (ES2018+). Transpile it if you need to support older environments.
The public API is backwards compatible. The changes below fix bugs and may affect behavior:
- GET URLs are no longer corrupted. Previously
https://could becomehttps:/when data was appended. Content-Typeis no longer duplicated whenpostis called more than once or headers are reused.onOpenednow fires. Handlers are attached before the request is opened.- All
2xxstatuses resolve (e.g.201,204); previously only200did. - URL validation is stricter. Invalid URLs are now rejected, and errors are thrown as
Errorobjects instead of strings. getwith an object now builds a query string instead of producing/[object Object].postwith a string no longer stringifies it a second time.- JSON parsing is more accurate. Falsy JSON values are no longer lost, and plain-text bodies such as
"123"are not converted to numbers. - New features:
timeout,withCredentials,responseType,signal,onUploadProgress, and errortype/ responsestatusfields.