Pretch
A lightweight and flexible fetch enhancement library that works with vanilla JavaScript, React, and Preact
Pretch
A lightweight and flexible fetch enhancement library that works with vanilla JavaScript, React, and Preact.
Check the Documentation in JSR
Features
- 🌐 Universal Compatibility – Seamlessly runs in all JavaScript environments, including Node.js, Bun, Deno, and browsers.
- ⚛️ Tailored for React & Preact – Enjoy dedicated hooks with full-feature integration for both frameworks.
- 🔧 Endlessly Customizable – Design custom fetch functions tailored to your unique requirements.
- 📝 TypeScript Native – Built-in TypeScript support ensures a flawless developer experience.
- 🛠 Powerful Middleware System – Leverage built-in middleware or create your own for extended functionality.
- 📦 Web API Driven – Crafted with a focus on modern Web APIs for seamless integration.
Packages
@pretch/core- Core functionality and middleware system@pretch/react- React hooks integration@pretch/preact- Preact hooks integration
Usage
Core (Vanilla Javascript) - @pretch/core
In the nexts examples, fetch is enhaced with a middleware that will be automatically add default headers to every request:
Create a custom fetch with behaviour enhaced through middleware and a base URL
import pretch from "@pretch/core";
import { applyMiddleware, defaultHeaders } from "@pretch/core/middleware";
const customFetch = pretch(
"https://jsonplaceholder.typicode.com/todos/",
applyMiddleware(
defaultHeaders({
"Content-Type": "application/json; charset=UTF-8",
}, {
strategy: "append",
}),
),
);
const getResponse = await customFetch.get("/1");
const createdTodo = await getResponse.json();
// The following request will keep the enhanced behaviour of adding default headers
const putResponse = await customFetch.put("/1", {
body: JSON.stringify({
title: "Updated todo",
body: "Same task",
userId: 1,
}),
});
const todoUpdated = await putResponse.json();
Create a custom fetch with behaviour enhaced through middleware to query different urls
import pretch from "@pretch/core";
import { applyMiddleware, defaultHeaders } from "@pretch/core/middleware";
const customFetch = pretch(
applyMiddleware(
defaultHeaders({
"Content-Type": "application/json; charset=UTF-8",
}, {
strategy: "append",
}),
),
);
const firstResponse = await customFetch("https://example.com/api/task");
const todo = await firstResponse.json();
const secondResponse = await customFetch(
"https://otherexample.com/api/auth/sing-in",
);
const user = await secondResponse.json();
Built-in middlewares
Pretch provides a built-in enhancer to apply middlewares on each request
Validate Status
import pretch from "@pretch/core";
import { applyMiddleware, validateStatus } from "@pretch/core/middleware";
const customFetch = pretch(
applyMiddleware(
validateStatus({
validate: (status) => 200 <= status && status <= 399,
errorFactory: (status, request, response) =>
new Error(`Error. Status code: ${status}`),
shouldCancelBody: true,
}),
),
);
Retry
import pretch from "@pretch/core";
import { applyMiddleware, retry } from "@pretch/core/middleware";
const customFetch = pretch(
applyMiddleware(
retry({
maxRetries: 2,
delay: 1_500,
}),
),
);
Default Headers
import pretch from "@pretch/core";
import { applyMiddleware, defaultHeaders } from "@pretch/core/middleware";
const customFetch = pretch(
applyMiddleware(
defaultHeaders({
"Content-Type": "application/json; charset=UTF-8",
}, {
strategy: "set", // Optional, by default the headers appended
}),
),
);
Authorization
import pretch from "@pretch/core";
import { applyMiddleware, authorization } from "@pretch/core/middleware";
const customFetch = pretch(
applyMiddleware(
authorization(
"123456789abcdef",
"bearer",
{
shouldApplyToken: (request: Request) =>
new URL(request.url).pathname.startsWith("/api/"),
},
),
),
);
Logging
import pretch from "@pretch/core";
import {
applyMiddleware,
type ErrorLogData,
logging,
type RequestLogData,
type ResponseLogData,
} from "@pretch/core/middleware";
const customFetch = pretch(
applyMiddleware(
logging({
onRequest: async ({ request }: RequestLogData) => {
console.log(`Starting request to ${request.url}`);
},
onResponse: async ({ response }: ResponseLogData) => {
console.log(`Received response with status ${response.status}`);
},
onCatch: async ({ error }: ErrorLogData) => {
console.error(`Request failed:`, error);
},
}),
),
);
Proxy
import pretch from "@pretch/core";
import { applyMiddleware, proxy } from "@pretch/core/middleware";
const customFetch = pretch(
applyMiddleware(
proxy(
"/api", // Forward all requests starting with /api
"https://api.example.com",
{
pathRewrite: (path: string) => path.replace(/^\/api/, ""), // Remove /api prefix
},
),
),
);
React integration(@pretch/react) and Preact integration(@pretch/preact)
The React and Preact integration delivers powerful hooks for both automatic and manual fetching, complete with built-in state management. Both packages share a unified API and identical features, with the only difference being the package source for importing the hooks.
Key Features of the Hooks:
- 🔄 Loading & Error State Management – Effortlessly track request states with built-in tools.
- 🛡 Type-Safe Response Handling – Enjoy confidence in your data with robust TypeScript support.
- ⚙️ Request Enhancement – Easily customize and optimize fetch requests to meet your needs.
- 🛠 Seamless @pretch/core Integration – Fully compatible with middlewares, enhancers, and other advanced features provided by the @pretch/core package.
Fetching with useFetch
useFetch automatically fetches the data when the component mounts. You can
refetch the data when the refetch function is called.
import { useFetch } from "@pretch/react";
function MyComponent() {
const { data, loading, error, refetch } = useFetch(
"https://api.example.com/todos/1",
);
const handleClick = () => {
refetch();
};
return (
<div>
{loading ? "Loading..." : "Data: " + JSON.stringify(data)}
{error && <p>Error: {error.message}</p>}
<button onClick={handleClick} disabled={loading}>Refresh</button>
</div>
);
}
Fetching with useLazyFetch
useLazyFetch fetches the data manually. You can trigger the fetch with the
fetchData function.
import { useLazyFetch } from "@pretch/react";
function MyComponent() {
const { data, loading, error, fetchData } = useLazyFetch({
url: "https://api.example.com/todos/1",
});
const handleClick = () => {
fetchData({
newOptions: {
method: "POST",
body: JSON.stringify({ title: "New Todo" }),
},
});
};
return (
<div>
{loading ? "Loading..." : "Data: " + JSON.stringify(data)}
{error && <p>Error: {error.message}</p>}
<button onClick={handleClick}>Create Todo</button>
</div>
);
}
Fetching with useQuery
A hook that provides a set of type-safe HTTP method functions (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) for making requests to a base URL. It includes built-in state management using signals to track loading states and errors.
import { useQuery } from "@pretch/react";
interface Todo {
id: number;
title: string;
completed: boolean;
}
function TodoExample() {
const { get, post } = useQuery("https://api.example.com");
const handleFetch = async () => {
const { data, loading, error } = await get<Todo>("/todos/1");
if (error) {
console.error("Failed to fetch:", error);
return;
}
if (data) {
console.log("Todo:", data.title);
}
};
const handleCreate = async () => {
const { data, error } = await post<Todo>("/todos", {
body: JSON.stringify({
title: "New Todo",
completed: false,
}),
});
if (data) {
console.log("Created todo:", data);
}
};
return (
<div>
<button onClick={handleFetch}>Fetch Todo</button>
<button onClick={handleCreate}>Create Todo</button>
</div>
);
}
The hook provides all standard HTTP methods (get, post, put, patch,
delete, head, options) that return a promise containing:
data: The parsed response dataloading: Boolean indicating if request is in progresserror: Error object if request failed (or null)
Each method supports URL parameters and request options, with full TypeScript support for response types.
Enhanced the fetching of useFetch, useLazyFetch and useQuery
The hook supports request enhancement through enhancer functions for customizing request behavior:
Custom enhancer: Implement your own enhancer function to modify the request behavior before it is sent.
import type { Enhancer, Handler } from "@pretch/core";
import { useFetch } from "@pretch/react";
function authHeaderEnhancer(handler: Handler) {
return async (request: Request) => {
const modifiedRequest = new Request(request, {
headers: {
...request.headers,
"Authorization": "Bearer my-token",
},
});
return handler(modifiedRequest);
};
}
function MyComponent() {
const { data } = useFetch("https://example.com", {
enhancer: authHeaderEnhancer,
});
return (
<div>
Data: {JSON.stringify(data)}
</div>
);
}
Built-in middlewares: Otherwise, Pretch provides a built-in enhancer to apply middlewares on each request, including built-in middlewares.
import {
applyMiddleware,
authorization,
defaultHeaders,
retry,
} from "@pretch/core";
import { useFetch, useLazyFetch, useQuery } from "@precth/react";
function TodoList() {
const { data, loading, error, refetch } = useFetch(
"https://api.example.com/todos/",
{
enhancer: applyMiddleware(
retry({
maxRetries: 2,
delay: 500,
}),
authorization("your-token", "bearer"),
),
},
);
const handleClick = () => { refetch() };
return (
<div>
{loading ? <span>Loading...</span> : (
<ul>
{data.map((todo) => {
<li>{todo.title}</li>;
})}
</ul>
)}
{error && <p>Error: {error.message}</p>}
<button onClick={handleClick}>Refresh</button>
</div>
);
}
function CreateTodo() {
const {data, fetchData, error, loading} = useLazyFetch({
url: "https://api.example.com/todos/",
enhancer: applyMiddleware(
defaultHeaders({
"Content-Type": "application/json; charset=UTF-8",
}),
),
});
const handleSubmit = async (event: SubmitEvent ) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
fetchData({
newOptions: {
method: "POST",
body: JSON.stringify(Object.fromEntries(formData.entries()))
}
})
};
return (
<form onSubmit={handleSubmit}>
<input name="title" >
<button>Create</button>
</form>
);
}
Why Pretch?
Struggling to find a simple yet customizable fetching hook library for Preact, I created @pretch/preact, focusing on ease of use, minimal abstraction, and alignment with Web APIs. This evolved into @pretch/core, a versatile package for fetch customization with built-in middlewares and enhancers, later extended to @pretch/react and @pretch/preact for React and Preact integration.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Credits
Created by EGAMAGZ
License
MIT License
TODO
- Create useQuery hook inspired on @tanstack/react-query and redux query
- Develop and automatize tests for @pretch/preact and @pretch/react