Gathering detailed insights and metrics for return-fetch
Gathering detailed insights and metrics for return-fetch
Gathering detailed insights and metrics for return-fetch
Gathering detailed insights and metrics for return-fetch
return-fetch-json
An extended function of return-fetch ot serialize request body and deserialize response body as object.
fetch-bluebird
patch polyfilled and native fetch to return bluebird promises
big-fetch
big fetch will return a primose with a abort function and can set a timeout
fetch-type-provider
A package that fetches a json and return the properties with it types
⛓️ A simple and powerful high order function to extend fetch for baseUrl, default headers, and interceptors.
npm install return-fetch
Typescript
Module System
Node Version
NPM Version
TypeScript (91.45%)
Shell (5.75%)
JavaScript (2.72%)
Makefile (0.08%)
Love this project? Help keep it running — sponsor us today! 🚀
Total Downloads
50,250
Last Day
84
Last Week
579
Last Month
5,369
Last Year
44,629
157 Stars
120 Commits
11 Forks
1 Watching
1 Branches
4 Contributors
Minified
Minified + Gzipped
Latest Version
0.4.7
Package Id
return-fetch@0.4.7
Unpacked Size
64.07 kB
Size
13.22 kB
File Count
12
NPM Version
10.8.2
Node Version
22.8.0
Publised On
19 Jan 2025
Cumulative downloads
Total Downloads
Last day
-67.6%
84
Compared to previous day
Last week
-57.2%
579
Compared to previous week
Last month
0.1%
5,369
Compared to previous month
Last year
694%
44,629
Compared to previous year
A simple and powerful high order function to extend fetch
for baseUrl, default headers, and
interceptors.
See interactive documentation
or
demo.
1import returnFetch from "return-fetch"; 2 3const fetchExtended = returnFetch({ 4 baseUrl: "https://jsonplaceholder.typicode.com", 5 headers: {Accept: "application/json"}, 6 interceptors: { 7 request: async (args) => { 8 console.log("********* before sending request *********"); 9 console.log("url:", args[0].toString()); 10 console.log("requestInit:", args[1], "\n\n"); 11 return args; 12 }, 13 14 response: async (requestArgs, response) => { 15 console.log("********* after receiving response *********"); 16 console.log("url:", requestArgs[0].toString()); 17 console.log("requestInit:", requestArgs[1], "\n\n"); 18 return response; 19 }, 20 }, 21}); 22 23fetchExtended("/todos/1", {method: "GET"}) 24 .then((it) => it.text()) 25 .then(console.log);
Output
********* before sending request *********
url: https://jsonplaceholder.typicode.com/todos/1
requestInit: { method: 'GET', headers: { Accept: 'application/json' } }
********* after receiving response *********
url: https://jsonplaceholder.typicode.com/todos/1
requestInit: { method: 'GET', headers: { Accept: 'application/json' } }
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
The Next.js framework(which I love so much) v13 App Router uses
its own fetch
implementation that extends node-fetch
to
do server side things like caching. I was accustomed to using Axios for API calls, but
I have felt that now is the time to replace Axios with fetch
finally. The most disappointing aspect I found when
trying to replace Axios with fetch
was that fetch
does not have any interceptors. I thought surely someone
must have implemented it, so I searched for libraries. However, there was no library capable of handling various
situations, only one that could add a single request and response interceptors to the global fetch
. That is the
reason why I decided to implement it myself.
In implementing the fetch
interceptors, I considered the following points:
fetch
(e.g. Node.js, Web Browsers, React
Native, Web Workers).return-fetch
will provide minimal functionality. Users should be
able to extend fetch as they wish.fetch
function created by return-fetch
should
be able to replace the standard fetch
function anywhere without any problems.fetch
polyfills.Via npm
1npm install return-fetch
Via yarn
1yarn add return-fetch
Via pnpm
1pnpm add return-fetch
1<!-- 2 Pick your favourite CDN: 3 - https://unpkg.com/return-fetch 4 - https://cdn.jsdelivr.net/npm/return-fetch 5 - https://www.skypack.dev/view/return-fetch 6 - … 7--> 8 9<!-- UMD import as window.returnFetch --> 10<script src="https://unpkg.com/return-fetch"></script> 11 12<!-- Modern import --> 13<script type="module"> 14 import returnFetch from 'https://cdn.skypack.dev/return-fetch/dist/index.js' 15 16 // ... // 17</script>
Run on Stickblitz.
https://return-fetch.myeongjae.kim/docs/types/ReturnFetchDefaultOptions.html
1import returnFetch, { ReturnFetch } from "return-fetch"; 2import { displayLoadingIndicator, hideLoadingIndicator } from "@/your/adorable/loading/indicator"; 3 4// Write your own high order function to display/hide loading indicator 5const returnFetchWithLoadingIndicator: ReturnFetch = (args) => returnFetch({ 6 ...args, 7 interceptors: { 8 request: async (args) => { 9 setLoading(true); 10 return args; 11 }, 12 response: async (requestArgs, response) => { 13 setLoading(false); 14 return response; 15 }, 16 }, 17}) 18 19// Create an extended fetch function and use it instead of the global fetch. 20export const fetchExtended = returnFetchWithLoadingIndicator({ 21 // default options 22}); 23 24//////////////////// Use it somewhere //////////////////// 25fetchExtended("/sample/api");
1import returnFetch, { ReturnFetch } from "return-fetch"; 2 3// Write your own high order function to throw an error if a response status is greater than or equal to 400. 4const returnFetchThrowingErrorByStatusCode: ReturnFetch = (args) => returnFetch({ 5 ...args, 6 interceptors: { 7 response: async (_, response) => { 8 if (response.status >= 400) { 9 throw await response.text().then(Error); 10 } 11 12 return response; 13 }, 14 }, 15}) 16 17// Create an extended fetch function and use it instead of the global fetch. 18export const fetchExtended = returnFetchThrowingErrorByStatusCode({ 19 // default options 20}); 21 22//////////////////// Use it somewhere //////////////////// 23fetchExtended("/sample/api/400").catch((e) => alert(e.message));
You can import below example code because it is published as a separated npm package: return-fetch-json
1import returnFetch, { FetchArgs, ReturnFetchDefaultOptions } from "return-fetch"; 2 3// Use as a replacer of `RequestInit` 4type JsonRequestInit = Omit<NonNullable<FetchArgs[1]>, "body"> & { body?: object }; 5 6// Use as a replacer of `Response` 7export type ResponseGenericBody<T> = Omit< 8 Awaited<ReturnType<typeof fetch>>, 9 keyof Body | "clone" 10> & { 11 body: T; 12}; 13 14export type JsonResponse<T> = T extends object 15 ? ResponseGenericBody<T> 16 : ResponseGenericBody<string>; 17 18 19// this resembles the default behavior of axios json parser 20// https://github.com/axios/axios/blob/21a5ad34c4a5956d81d338059ac0dd34a19ed094/lib/defaults/index.js#L25 21const parseJsonSafely = (text: string): object | string => { 22 try { 23 return JSON.parse(text); 24 } catch (e) { 25 if ((e as Error).name !== "SyntaxError") { 26 throw e; 27 } 28 29 return text.trim(); 30 } 31}; 32 33// Write your own high order function to serialize request body and deserialize response body. 34export const returnFetchJson = (args?: ReturnFetchDefaultOptions) => { 35 const fetch = returnFetch(args); 36 37 return async <T>( 38 url: FetchArgs[0], 39 init?: JsonRequestInit, 40 ): Promise<JsonResponse<T>> => { 41 const response = await fetch(url, { 42 ...init, 43 body: init?.body && JSON.stringify(init.body), 44 }); 45 46 const body = parseJsonSafely(await response.text()) as T; 47 48 return { 49 headers: response.headers, 50 ok: response.ok, 51 redirected: response.redirected, 52 status: response.status, 53 statusText: response.statusText, 54 type: response.type, 55 url: response.url, 56 body, 57 } as JsonResponse<T>; 58 }; 59}; 60 61// Create an extended fetch function and use it instead of the global fetch. 62export const fetchExtended = returnFetchJson({ 63 // default options 64}); 65 66//////////////////// Use it somewhere //////////////////// 67export type ApiResponse<T> = { 68 status: number; 69 statusText: string; 70 data: T; 71}; 72 73fetchExtended<ApiResponse<{ message: string }>>("/sample/api/echo", { 74 method: "POST", 75 body: {message: "Hello, world!"}, // body should be an object. 76}).then(it => it.body);
Because of the recursive type definition, you can chain extended returnFetch functions as many as you want. It allows you to write extending functions which are responsible only for a single feature. It is a good practice to stick to the Single Responsibility Principle and writing a reusable function to write clean code.
1import { 2 returnFetchJson, 3 returnFetchThrowingErrorByStatusCode, 4 returnFetchWithLoadingIndicator 5} from "@/your/customized/return-fetch"; 6 7/* 8 Compose high order functions to create your awesome fetch. 9 1. Add loading indicator. 10 2. Throw an error when a response's status code is equal to 400 or greater. 11 3. Serialize request body and deserialize response body as json and return it. 12*/ 13export const fetchExtended = returnFetchJson({ 14 fetch: returnFetchThrowingErrorByStatusCode({ 15 fetch: returnFetchWithLoadingIndicator({ 16 // default options 17 }), 18 }), 19}); 20 21//////////////////// Use it somewhere //////////////////// 22fetchExtended("/sample/api/echo", { 23 method: "POST", 24 body: {message: "this is an object of `ApiResponse['data']`"}, // body should be an object. 25}).catch((e) => alert(e.message));
fetch
implementationfetch
has been added since Node.js v17.5 as an experimental feature,
also available from Node.js v16.15 and
still experimental(29 JUL 2023, v20.5.0). You can use
'node-fetch' as a polyfill for Node.js v16.15 or lower,
or 'whatwg-fetch' for old web browsers, or
'cross-fetch' for both web browser and Node.js.
Next.js has already included 'node-fetch' and they extend it for server-side things like caching.
Whatever a fetch
you use, you can use return-fetch
as long as the fetch
you use is compatible with the
Fetch API.
node-fetch
I implemented a simple proxy for https://postman-echo.com with node-fetch
as an example using
Next.js route handler.
1// src/app/sample/api/proxy/postman-echo/node-fetch/[[...path]]/route.ts
2import { NextRequest } from "next/server";
3import nodeFetch from "node-fetch";
4import returnFetch, { ReturnFetchDefaultOptions } from "return-fetch";
5
6process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; // to turn off SSL certificate verification on server side
7const pathPrefix = "/sample/api/proxy/postman-echo/node-fetch";
8
9export async function GET(request: NextRequest) {
10 const {nextUrl, method, headers} = request;
11
12 const fetch = returnFetch({
13 // Use node-fetch instead of global fetch
14 fetch: nodeFetch as ReturnFetchDefaultOptions["fetch"],
15 baseUrl: "https://postman-echo.com",
16 });
17
18 const response = await fetch(nextUrl.pathname.replace(pathPrefix, ""), {
19 method,
20 headers,
21 });
22
23 return new Response(response.body, {
24 status: response.status,
25 statusText: response.statusText,
26 headers: response.headers,
27 });
28}
Send a request to the proxy route.
1import { 2 returnFetchJson, 3 returnFetchThrowingErrorByStatusCode, 4 returnFetchWithLoadingIndicator 5} from "@/your/customized/return-fetch"; 6 7export const fetchExtended = returnFetchJson({ 8 fetch: returnFetchThrowingErrorByStatusCode({ 9 fetch: returnFetchWithLoadingIndicator({ 10 baseUrl: "https://return-fetch.myeongjae.kim", 11 }), 12 }), 13}); 14 15//////////////////// Use it somewhere //////////////////// 16fetchExtended( 17 "/sample/api/proxy/postman-echo/node-fetch/get", 18 { 19 headers: { 20 "X-My-Custom-Header": "Hello World!" 21 } 22 }, 23);
whatwg-fetch
whatwg-fetch
is a polyfill for browsers. I am going to send a request using whatwg-fetch
to the proxy route.
1import {
2 returnFetchJson,
3 returnFetchThrowingErrorByStatusCode,
4 returnFetchWithLoadingIndicator
5} from "@/your/customized/return-fetch";
6import { fetch as whatwgFetch } from "whatwg-fetch";
7
8export const fetchExtended = returnFetchJson({
9 fetch: returnFetchThrowingErrorByStatusCode({
10 fetch: returnFetchWithLoadingIndicator({
11 fetch: whatwgFetch, // use whatwgFetch instead of browser's global fetch
12 baseUrl: "https://return-fetch.myeongjae.kim",
13 }),
14 }),
15});
16
17//////////////////// Use it somewhere ////////////////////
18fetchExtended(
19 "/sample/api/proxy/postman-echo/node-fetch/get",
20 {
21 headers: {
22 "X-My-Custom-Header": "Hello World!"
23 }
24 },
25);
cross-fetch
I implemented a simple proxy for https://postman-echo.com with cross-fetch
as an example using
Next.js route handler.
1// src/app/sample/api/proxy/postman-echo/cross-fetch/[[...path]]/route.ts
2import { NextRequest } from "next/server";
3import crossFetch from "cross-fetch";
4import returnFetch from "return-fetch";
5
6process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; // to turn off SSL certificate verification on server side
7const pathPrefix = "/sample/api/proxy/postman-echo/cross-fetch";
8
9export async function GET(request: NextRequest) {
10 const {nextUrl, method, headers} = request;
11
12 const fetch = returnFetch({
13 fetch: crossFetch, // Use cross-fetch instead of built-in Next.js fetch
14 baseUrl: "https://postman-echo.com",
15 });
16
17 const response = await fetch(nextUrl.pathname.replace(pathPrefix, ""), {
18 method,
19 headers,
20 });
21
22 return new Response(response.body, {
23 status: response.status,
24 statusText: response.statusText,
25 headers: response.headers,
26 });
27}
Send a request to the proxy route using cross-fetch
on the client-side also.
1import {
2 returnFetchJson,
3 returnFetchThrowingErrorByStatusCode,
4 returnFetchWithLoadingIndicator
5} from "@/your/customized/return-fetch";
6import crossFetch from "cross-fetch";
7
8export const fetchExtended = returnFetchJson({
9 fetch: returnFetchThrowingErrorByStatusCode({
10 fetch: returnFetchWithLoadingIndicator({
11 fetch: crossFetch, // Use cross-fetch instead of browser's global fetch
12 baseUrl: "https://return-fetch.myeongjae.kim",
13 }),
14 }),
15});
16
17//////////////////// Use it somewhere ////////////////////
18fetchExtended(
19 "/sample/api/proxy/postman-echo/node-fetch/get",
20 {
21 headers: {
22 "X-My-Custom-Header": "Hello World!"
23 }
24 },
25);
fetch
I implemented a simple proxy for https://postman-echo.com with Next.js built-in fetch
as an example using
Next.js route handler.
1// src/app/sample/api/proxy/postman-echo/nextjs-fetch/[[...path]]/route.ts
2import { NextRequest } from "next/server";
3import returnFetch from "return-fetch";
4
5process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; // to turn off SSL certificate verification on server side
6const pathPrefix = "/sample/api/proxy/postman-echo/nextjs-fetch";
7
8export async function GET(request: NextRequest) {
9 const {nextUrl, method, headers} = request;
10
11 const fetch = returnFetch({
12 // omit fetch option to use Next.js built-in fetch
13 baseUrl: "https://postman-echo.com",
14 });
15
16 const response = await fetch(nextUrl.pathname.replace(pathPrefix, ""), {
17 method,
18 headers,
19 });
20
21 return new Response(response.body, {
22 status: response.status,
23 statusText: response.statusText,
24 headers: response.headers,
25 });
26}
Send a request to the proxy route using web browser default fetch
.
1import { 2 returnFetchJson, 3 returnFetchThrowingErrorByStatusCode, 4 returnFetchWithLoadingIndicator 5} from "@/your/customized/return-fetch"; 6 7export const fetchExtended = returnFetchJson({ 8 fetch: returnFetchThrowingErrorByStatusCode({ 9 fetch: returnFetchWithLoadingIndicator({ 10 baseUrl: "https://return-fetch.myeongjae.kim", 11 }), 12 }), 13}); 14 15//////////////////// Use it somewhere //////////////////// 16fetchExtended( 17 "/sample/api/proxy/postman-echo/nextjs-fetch/get", 18 { 19 headers: { 20 "X-My-Custom-Header": "Hello World!" 21 } 22 }, 23);
(I have not written a documents for React Native yet, but it surely works with React Native becuase it does not have
any dependencies on a specific fetch
implementation.)
fetch
with your customized returnFetch
1import { 2 returnFetchJson, 3 returnFetchThrowingErrorByStatusCode, 4 returnFetchWithLoadingIndicator 5} from "@/your/customized/return-fetch"; 6 7// save global fetch reference. 8const globalFetch = fetch; 9export const fetchExtended = returnFetchThrowingErrorByStatusCode({ 10 fetch: returnFetchWithLoadingIndicator({ 11 fetch: globalFetch, // use global fetch as a base. 12 }), 13}); 14 15// replace global fetch with your customized fetch. 16window.fetch = fetchExtended; 17 18//////////////////// Use it somewhere //////////////////// 19fetch("/sample/api/echo", { 20 method: "POST", 21 body: JSON.stringify({message: "Hello, world!"}) 22}).catch((e) => alert(e.message));
URL
or Request
object as a first argument of fetch
The type of a first argument of fetch
is Request | string | URL
and a second argument is
RequestInit | undefined
. Through above examples, we used a string as a first argument of fetch
.
return-fetch
is also able to handle a Request
or URL
object as a first argument. It does not restrict
any features of fetch
.
Be careful! the default options' baseURL does not applied to a URL
or Request
object. Default headers are
still applied to a Request
object as you expected.
URL
object as a first argumentEven a baseUrl
is set to 'https://google.com', it is not applied to a URL
object. An URL
object cannot be
created if an argument does not have origin. You should set a full URL
to a URL
object, so a baseUrl
will
be ignored.
1import returnFetch from "return-fetch";
2
3const fetchExtended = returnFetch({
4 baseUrl: "https://google.com",
5 headers: {
6 "Content-Type": "application/json",
7 "Accept": "application/json",
8 "X-My-Header": "Hello, world!",
9 },
10});
11
12fetchExtended(
13 /*
14 Even a baseURL is set to 'https://google.com', it is not applied to a URL object.
15 An URL object cannot be created if an argument does not have origin.
16 You should set a full URL to a URL object, so a baseURL will be ignored.
17 */
18 new Request(new URL("https://return-fetch.myeongjae.kim/sample/api/proxy/postman-echo/nextjs-fetch/post"), {
19 method: "PUT",
20 body: JSON.stringify({message: "overwritten by requestInit"}),
21 headers: {
22 "X-My-Headers-In-Request-Object": "Works well!",
23 },
24 }),
25 {
26 method: "POST",
27 body: JSON.stringify({message: "Hello, world!"}),
28 },
29)
30 .then((it) => it.json())
31 .then(console.log);
Request
object as a first argumentEven a baseUrl
is set to 'https://google.com', it is not applied to a Request
object. While creating a
Request
object, an origin is set to 'https://return-fetch.myeongjae.kim', which is the origin of this page,
so baseUrl
will be ignored.
On Node.js, a Request
object cannot be created without an origin, same as URL
object.
1import returnFetch from "return-fetch";
2
3const fetchExtended = returnFetch({
4 baseUrl: "https://google.com",
5 headers: {
6 "Content-Type": "application/json",
7 "Accept": "application/json",
8 "X-My-Header": "Hello, world!",
9 },
10});
11
12fetchExtended(
13 /*
14 Even a baseURL is set to 'https://google.com', it is not applied to a Request object.
15 While creating a Request object, an origin is set to 'https://return-fetch.myeongjae.kim',
16 which is the origin of this page, so baseURL will be ignored.
17 */
18 new Request("/sample/api/proxy/postman-echo/node-fetch/post", {
19 method: "PUT",
20 body: JSON.stringify({message: "overwritten by requestInit"}),
21 headers: {
22 "X-My-Headers-In-Request-Object": "Works well!",
23 },
24 }),
25 {
26 method: "POST",
27 body: JSON.stringify({message: "Hello, world!"}),
28 },
29)
30 .then((it) => it.json())
31 .then(console.log);
Interceptors are async functions. You can make an async call in interceptors. This example shows how to retry a request when a response status is 401.
1let retryCount = 0; 2 3const returnFetchRetry: ReturnFetch = (args) => returnFetch({ 4 ...args, 5 interceptors: { 6 response: async (response, requestArgs, fetch) => { 7 if (response.status !== 401) { 8 return response; 9 } 10 11 console.log("not authorized, trying to get refresh cookie.."); 12 const responseToRefreshCookie = await fetch( 13 "https://httpstat.us/200", 14 ); 15 if (responseToRefreshCookie.status !== 200) { 16 throw Error("failed to refresh cookie"); 17 } 18 19 retryCount += 1; 20 console.log(`(#${retryCount}) succeeded to refresh cookie and retry request`); 21 return fetch(...requestArgs); 22 }, 23 }, 24}); 25 26const fetchExtended = returnFetchRetry({ 27 baseUrl: "https://httpstat.us", 28}); 29 30fetchExtended("/401") 31 .then((it) => it.text()) 32 .then((it) => `Response body: "${it}"`) 33 .then(console.log) 34 .then(() => console.log("\n Total counts of request: " + (retryCount + 1)))
If you nest returnFetchRetry
, you can retry a request more than once. When you nest 4 times, you can retry a
request 16 times (I know it is too much, but isn't it fun?).
1let retryCount = 0; 2 3// create a fetch function with baseUrl applied 4const fetchBaseUrlApplied = returnFetch({baseUrl: "https://httpstat.us"}); 5 6const returnFetchRetry: ReturnFetch = (args) => returnFetch({ 7 ...args, 8 // use fetchBaseUrlApplied as a default fetch function 9 fetch: args?.fetch ?? fetchBaseUrlApplied, 10 interceptors: { 11 response: async (response, requestArgs, fetch) => { 12 if (response.status !== 401) { 13 return response; 14 } 15 16 console.log("not authorized, trying to get refresh cookie.."); 17 const responseToRefreshCookie = await fetch("/200"); 18 if (responseToRefreshCookie.status !== 200) { 19 throw Error("failed to refresh cookie"); 20 } 21 22 retryCount += 1; 23 console.log(`(#${retryCount}) succeeded to refresh cookie and retry request`); 24 return fetch(...requestArgs); 25 }, 26 }, 27}); 28 29const nest = ( 30 remaining: number, 31 providedFetch = fetchBaseUrlApplied, 32): ReturnType<ReturnFetch> => 33 remaining > 0 34 ? nest(remaining - 1, returnFetchRetry({fetch: providedFetch})) 35 : providedFetch; 36 37// nest 4 times -> 2^4 = 16 38const fetchExtended = nest(4); 39 40fetchExtended("/401") 41 .then((it) => it.text()) 42 .then((it) => `Response body: "${it}"`) 43 .then(console.log) 44 .then(() => console.log("\n Total counts of request: " + (retryCount + 1)))
MIT © Myeongjae Kim
No vulnerabilities found.
No security vulnerabilities found.