http-fetch-promise
v1.0.0
Published
A lightweight fetch() wrapper that normalizes all responses into resolved or rejected promises, designed for use with Redux Toolkit's createAsyncThunk()
Maintainers
Readme
http-fetch-promise
A lightweight fetch() wrapper that normalizes all responses into resolved or rejected promises. Designed for use with Redux Toolkit's createAsyncThunk().
Zero dependencies. Works in browsers, Node 18+, and SSR frameworks.
Install
npm install http-fetch-promiseWhy
createAsyncThunk() dispatches a rejected action when a thunk throws or returns a rejected promise, but the error payload is run through miniSerializeError, which strips everything except name, message, stack, and code. Custom fields like status, data, and url are lost.
http-fetch-promise solves this by catching all errors internally and rejecting with plain objects that have a normalized structure. Thunks can catch these rejections and pass them directly to thunkAPI.rejectWithValue(), preserving the full payload on action.payload in slice reducers.
Usage
Import styles
// Default import
import api from 'http-fetch-promise';
// Named imports
import { api, get, post, put, del } from 'http-fetch-promise';Both styles are supported. The named exports (get, post, put, del) are the same functions available on api.get(), api.post(), api.put(), and api.delete().
delis used as the named export becausedeleteis a reserved word in JavaScript.
Basic requests
// GET (default method)
const response = await api('/api/users');
// Convenience methods
const user = await api.get('/api/users/1');
const created = await api.post('/api/users', { name: 'Marty' });
const updated = await api.put('/api/users/1', { name: 'Marty McFly' });
const removed = await api.delete('/api/users/1');With Redux Toolkit
import { createAsyncThunk } from '@reduxjs/toolkit';
import api from 'http-fetch-promise';
export const fetchUsers = createAsyncThunk('users/fetch', async (_, thunkAPI) => {
try {
const response = await api.get('/api/users');
return response.data;
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
});In the slice:
extraReducers: (builder) => {
builder
.addCase(fetchUsers.fulfilled, (state, action) => {
state.users = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
// action.payload has the full normalized error object
state.error = action.payload;
});
}Custom headers
await api.get('/api/protected', {
headers: { Authorization: 'Bearer token' },
});Custom headers are merged with the defaults (Content-Type: application/json, Accept: application/json). Custom values override defaults when keys overlap.
Request bodies
Plain objects are JSON-serialized automatically:
await api.post('/api/users', { name: 'Marty' });
// body sent as: '{"name":"Marty"}'Native fetch body types are passed through as-is, and the Content-Type header is removed so the browser can set the correct one:
const formData = new FormData();
formData.append('file', selectedFile);
await api.post('/api/upload', formData);Supported passthrough types: FormData, Blob, URLSearchParams, ArrayBuffer, ReadableStream.
String bodies are sent as-is without transformation.
Response shapes
Success (2xx)
Resolves with:
{
status: 200,
statusText: 'OK',
ok: true,
data: { ... }, // parsed JSON, text string, or undefined for 204
url: 'https://api.example.com/users'
}The data field is automatically parsed based on the response Content-Type:
application/jsonresponses are parsed as JSON- All other content types are returned as text
204 No Contentresponses setdatatoundefined
HTTP error (non-2xx)
Rejects with the same shape, but ok is false:
{
status: 404,
statusText: 'Not Found',
ok: false,
data: { error: 'User not found' },
url: 'https://api.example.com/users/999'
}Network error
Rejects when fetch() itself fails (DNS failure, offline, CORS):
{
name: 'TypeError',
message: 'Failed to fetch',
type: 'Client Error'
}Parse error
Rejects when the response body can't be parsed (e.g. malformed JSON with a Content-Type: application/json header):
{
status: 200,
statusText: 'OK',
ok: true,
data: null,
url: 'https://api.example.com/data',
parseError: 'Unexpected token < in JSON at position 0'
}Defaults
| Setting | Default | Overridable |
|---|---|---|
| Method | GET | Yes, via config.method or convenience methods |
| Content-Type | application/json | Yes, via config.headers |
| Accept | application/json | Yes, via config.headers |
| Credentials | same-origin | Yes, via config.credentials |
API
api(endpoint, config?)
The base function. Accepts any valid fetch configuration alongside body.
api.get(endpoint, config?) / get(endpoint, config?)
api.post(endpoint, body, config?) / post(endpoint, body, config?)
api.put(endpoint, body, config?) / put(endpoint, body, config?)
api.delete(endpoint, config?) / del(endpoint, config?)
Compatibility
Requires a runtime with a global fetch implementation:
- All modern browsers
- Node.js 18+
- SSR frameworks (Next.js, Nuxt, SvelteKit, etc.)
Testing
npm test