{"id":21937605,"url":"https://github.com/cap32/fetch-extra","last_synced_at":"2026-05-11T07:46:58.248Z","repository":{"id":57234559,"uuid":"113827263","full_name":"Cap32/fetch-extra","owner":"Cap32","description":"🏹 Extra features for whatwg fetch and Request (Node.js and browser)","archived":false,"fork":false,"pushed_at":"2018-11-11T15:50:18.000Z","size":341,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-08T03:02:15.031Z","etag":null,"topics":["ajax","browser","fetch","node","promise","query","request","transformer","xhr"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Cap32.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-11T07:33:17.000Z","updated_at":"2023-03-04T05:34:31.000Z","dependencies_parsed_at":"2022-09-15T04:12:30.820Z","dependency_job_id":null,"html_url":"https://github.com/Cap32/fetch-extra","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cap32%2Ffetch-extra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cap32%2Ffetch-extra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cap32%2Ffetch-extra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cap32%2Ffetch-extra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Cap32","download_url":"https://codeload.github.com/Cap32/fetch-extra/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244967899,"owners_count":20540030,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","browser","fetch","node","promise","query","request","transformer","xhr"],"created_at":"2024-11-29T01:20:43.179Z","updated_at":"2026-05-11T07:46:58.212Z","avatar_url":"https://github.com/Cap32.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fetch-extra\n\n[![Build Status](https://travis-ci.org/Cap32/fetch-extra.svg?branch=master)](https://travis-ci.org/Cap32/fetch-extra)\n[![Coverage Status](https://coveralls.io/repos/github/Cap32/fetch-extra/badge.svg?branch=master)](https://coveralls.io/github/Cap32/fetch-extra?branch=master)\n[![License](https://img.shields.io/badge/license-MIT_License-brightgreen.svg?style=flat)](https://github.com/Cap32/fetch-extra/blob/master/LICENSE)\n\nExtra features for [whatwg fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) and [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) like `query` object, JSON `body`, timeout, [abort](https://developers.google.com/web/updates/2017/09/abortable-fetch), `transformers`. Works for browser and Node.js.\n\n## Table of Contents\n\n- [Table of Contents](#table-of-contents)\n- [Installations](#installations)\n- [fetch](#fetch)\n- [Request](#request)\n  - [New `Request#fetch(...options)` method](#new-requestfetchoptions-method)\n  - [Enhanced `url` option](#enhanced-url-option)\n  - [Enhanced `Request#clone(...options)` method](#enhanced-requestcloneoptions-method)\n  - [New `responseType` option](#new-responsetype-option)\n  - [New `query` option](#new-query-option)\n  - [Enhanced `body` option](#enhanced-body-option)\n  - [New `type` option](#new-type-option)\n  - [New `simple` option](#new-simple-option)\n  - [Polyfill `AbortController`](#polyfill-abortcontroller)\n  - [New `queryStringify` option](#new-querystringify-option)\n  - [New `queryParse` option](#new-queryparse-option)\n  - [New `queryTransformer` option](#new-querytransformer-option)\n  - [New `urlTransformer` option](#new-urltransformer-option)\n  - [New `headersTransformer` option](#new-headerstransformer-option)\n  - [New `bodyTransformer` option](#new-bodytransformer-option)\n  - [New `responseTransformer` option](#new-responsetransformer-option)\n  - [New `responseDataTransformer` option](#new-responsedatatransformer-option)\n  - [New `errorTransformer` option](#new-errortransformer-option)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Installations\n\nUsing npm:\n\n```bash\n$ npm install fetch-extra\n```\n\nOr using 1998 Script Tag:\n\n```html\n\u003cscript src=\"https://unpkg.com/fetch-extra@latest/dist/fetch-extra-umd.js\"\u003e\u003c/script\u003e\n(Module exposed as `fetchExtra`)\n```\n\n**To install fetch-extra with `window.fetch` polyfill, please istall `fetch-extra-polyfill` instead**\n\n## fetch\n\n```js\nimport fetch from \"fetch-extra\";\n(async function main() {\n  const url = \"https://swapi.co/api/people/\";\n  const res = await fetch(url, {\n    method: \"POST\",\n    type: \"json\" /* json Content-Type header */,\n    responseType: \"json\" /* short for `await res.json()` */,\n    query: { token: \"asdf\" } /* query object */,\n    body: {\n      /* json body object */\n      firstName: \"Luke\",\n      familyName: \"Skywalker\"\n    }\n  });\n  console.log(res.name); /* Luke Skywalker */\n})();\n```\n\n##### Syntax\n\n\u003e Promise\\\u003cResponse\\\u003e fetch(...options)\n\n`...options` \\\u003c...String|Object|Request\\\u003e\n\n- If `options` is a string, it is treated as a `URL`\n- If `options` is a object, it is treated as `Request` options. Checkout below for detail. All [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) options are supported\n\nLater `options` will similarly overwrite earlier ones.\n\n##### Description\n\n\u003e The Fetch API provides an interface for fetching resources.\n\n`fetch` syntax adapts to [WHATWG fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API):\n\n```js\nimport fetch from \"fetch-extra\";\n(async function main() {\n  const url = \"https://swapi.co/api/people/1/\";\n  const res = await fetch(url, { method: \"GET\" });\n  const luke = await res.json();\n  console.log(luke.name); /* Luke Skywalker */\n})();\n```\n\nBut there are some extra options.\n\n```js\nconst res = await fetch({\n  url: \"https://swapi.co/api/people/1/\",\n  query: { page: 32 },\n  responseType: \"json\",\n  timeout: 30000\n});\n```\n\nFor more extra options and usages, please checkout below.\n\n## Request\n\n```js\nimport { Request } from \"fetch-extra\";\n(async function main() {\n  const base = new Request({\n    url: \"https://swapi.co/api/\",\n    type: \"json\",\n    responseType: \"json\",\n    timeout: 30000,\n    async headersTransformer(headers) {\n        headers['access-token'] = await fakeGetToken(),\n        return headers;\n    },\n  });\n  const people = base.clone(\"/people\");\n  const starships = base.clone(\"/starships\");\n\n  const luke = await people.fetch(\"/1\");\n  const c3po = await people.fetch(\"/2\");\n  const starDestroyer = await starships.fetch(\"/3\");\n})();\n```\n\n##### Syntax\n\n\u003e \\\u003cRequest\\\u003e new Request(...options)\n\n`...options` \\\u003c...String|Object|Request\\\u003e\n\nAll options are the same with `fetch(...options)`.\n\n##### Description\n\n\u003e The Request interface of the Fetch API represents a resource request.\n\nRequest syntax also adapts to [WHATWG Request](https://developer.mozilla.org/en-US/docs/Web/API/Request).\n\n```js\nimport fetch, { Request } from \"fetch-extra\";\n(async function main() {\n  const url = \"https://swapi.co/api/people/1/\";\n  const request = new Request(url);\n  const res = await fetch(request);\n  const luke = await res.json();\n})();\n```\n\nBut there are some extra options and methods.\n\n##### Why\n\n`fetch()` is useful, but `new Request()` provides a way to inherit requests. It is recommended to create a base `Request` instance to share base url, `Content-Type` header, access token header, response type, error handler (by using [errorTransformer()](#new-errortransformer-option)), etc, and then `fetch()` or `clone()` the base request.\n\n### New `Request#fetch(...options)` method\n\nA shortcut for `fetch(request, ...options)`.\n\nAll options are the same with `fetch(...options)`.\n\n```js\nimport { Request } from \"fetch-extra\";\nconst request = new Request(url);\nconst res = await request.fetch();\nconst luke = await res.json();\n```\n\nFetching with options:\n\n```js\nimport { Request } from \"fetch-extra\";\nconst request = new Request(url);\nconst res = await request.fetch({ method: \"DELETE\" });\nconst luke = await res.json();\n```\n\nThe example above is equal to:\n\n```js\nimport fetch, { Request } from \"fetch-extra\";\nconst request = new Request(url);\nconst res = await fetch(request, { method: \"DELETE\" });\nconst luke = await res.json();\n```\n\n### Enhanced `url` option\n\n`URLs` could be composed.\n\n```js\nconst baseUrl = \"https://swapi.co/api/\";\nconst swRequest = new Request(baseUrl);\n\nconst lukeRes = await swRequest.fetch(\"/people/1/\");\n/* final URL will be \"https://swapi.co/api/people/1/\" */\n\nconst starShipRes = await swRequest.fetch(\"/starships/9/\");\n/* final URL will be \"https://swapi.co/api/starships/9/\" */\n```\n\nTo override earlier URL, just give a new URL starts with a protocol (like `http://` or `https://`):\n\n```js\nconst swRequest = new Request(\"https://swapi.co/\", options);\nconst pokeRes = swRequest.fetch(\"https://pokeapi.co/api/v2/\");\n/* final URL will be \"https://pokeapi.co/api/v2/\" */\n```\n\n### Enhanced `Request#clone(...options)` method\n\n```js\nconst baseRequest = new Request({\n  headers: { \"Content-Type\": \"application/json\" }\n});\n\nconst swRequest = baseRequest.clone(\"https://swapi.co/api/\");\nconst luke = await swRequest.fetch(\"/people/1/\");\nconst c3po = await swRequest.fetch(\"/people/2/\");\n\nconst pokeRequest = baseRequest.clone(\"https://pokeapi.co/api/v2/\");\nconst bulbasaur = await pokeRequest.fetch(\"/pokemon/1/\");\n```\n\nThe `...options` usages are the same with `fetch(...options)` and `new Request(...options)`\n\n### New `responseType` option\n\nReturning resolved data with specified type instead of `response` object.\n\n```js\nconst options = { responseType: \"json\" };\nconst luke = await swRequest.fetch(\n  options\n); /* \u003c-- no need `await res.json()` */\nconsole.log(luke.name); /* Luke Skywalker */\n```\n\nIn browser, `responseType` value could be one of `arrayBuffer`, `blob`, `formData`, `json` or `text`.\n\nIn Node.js, `formData` is NOT supported.\n\nIf `responseType` is `none`, it will return the original `response` object.\n\n### New `query` option\n\n```js\nconst results = await swRequest.fetch({ query: { search: \"luke\" } });\n/* final URL will be \"https://swapi.co/api/people?search=luke\" */\n```\n\n`query` could be JSON object or string (like `name=luke\u0026height=172`).\n\nIf `url` has search fields (like `https://swapi.co/api/people?search=luke`), query string will append to the search fields.\n\n### Enhanced `body` option\n\n```js\nconst results = await swRequest.fetch({\n  method: \"POST\",\n  body: { name: \"Luke Skywalker\" } /* \u003c-- could be a JSON */\n});\n/* final body will be '{\"name\":\"Luke Skywalker\"}' */\n```\n\n### New `type` option\n\n```js\nconst results = await swRequest.fetch({\n    method: 'POST',\n    type: 'form'\n    body: { name: 'Luke Skywalker' },\n});\n/* final body will be 'name=Luke%20Skywalker' */\n/* final header['Content-Type'] will be 'application/x-www-form-urlencoded' */\n```\n\n`type` value will auto set to headers `Content-Type`.\n\nValue `form` is short for `application/x-www-form-urlencoded`, and `json` is short for `application/json`.\n\n### New `simple` option\n\nWill throw error if `response` status is non-2xx.\n\n```js\nawait swRequest\n  .fetch({\n    simple: true,\n    url: \"/400\" /* simulate response with 400 HTTP status */\n  })\n  .catch(err =\u003e {\n    console.error(err); /* \u003c-- Error: Bad Request  */\n  });\n```\n\n### Polyfill `AbortController`\n\nBuilt-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) polyfill for [aborting fetch](https://developers.google.com/web/updates/2017/09/abortable-fetch).\n\n```js\nimport fetch, { AbortController } from \"fetch-extra\";\n(async function main() {\n  const abortController = new AbortController();\n  const fetchPromise = fetch(\"https://swapi.co/api/people/1\", {\n    signal: abortController.signal\n  });\n  abortController.abort();\n  await fetchPromise.catch(err =\u003e {\n    if (err.name === \"AbortError\") console.warn(\"Aborted.\");\n    else console.error(err.message);\n  });\n})();\n```\n\n### New `queryStringify` option\n\nSetting a custom function in charge of serializing `query` object.\n\n```js\nimport qs from \"qs\";\n\nconst request = new Request({\n  queryStringify: qs.stringify\n});\n```\n\nBy default, this function is [tiny-querystring](https://github.com/Cap32/tiny-querystring#stringifyobj) `stringify` function.\n\n### New `queryParse` option\n\nSetting a custom function in charge of parsing `query` string.\n\n```js\nimport qs from \"qs\";\n\nconst request = new Request({\n  queryParse: qs.parse\n});\n```\n\nBy default, this function is [tiny-querystring](https://github.com/Cap32/tiny-querystring#stringifyobj) `parse` function.\n\n### New `queryTransformer` option\n\nSetting a function to transform `query` object, should return a new `query` object. Will be called before fetching.\n\n```js\nconst baseRequest = new Request({\n    queryTransformer: (query) =\u003e { /* \u003c-- queryTransformer */\n        query.accessToken = '\u003cACCESS_TOKEN\u003e',\n        return query;\n    },\n});\nconst swRequest = baseRequest.clone('https://swapi.co/api/');\nconst results = await swRequest.fetch('/people', {\n    query: { search: 'luke' },\n});\n/* final URL will be \"https://swapi.co/api/people?search=luke\u0026accessToken=\u003cACCESS_TOKEN\u003e\" */\n```\n\nAll transformers could return promises.\n\n```js\nconst baseRequest = new Request({\n    async queryTransformer(query) { /* \u003c-- async queryTransformer */\n        query.accessToken = await getTokenAsync(),\n        return query;\n    },\n});\n/* ... */\n```\n\n### New `urlTransformer` option\n\nLike `queryTransformer`, but transform `url`.\n\n### New `headersTransformer` option\n\nLike `queryTransformer`, but transform `headers`.\n\n### New `bodyTransformer` option\n\nLike `queryTransformer`, but transform `body`.\n\n### New `responseTransformer` option\n\nTransform [response](https://developer.mozilla.org/en-US/docs/Web/API/Response) instance.\n\n```js\nconst baseRequest = new Request({\n  responseType: \"json\",\n  responseTransformer(response) {\n    /* \u003c-- responseTransformer */\n    if (response.status === 404) {\n      throw new Error(\"Page not found\");\n    }\n    return response;\n  }\n});\n/* ... */\n```\n\n### New `responseDataTransformer` option\n\nLike `responseTransformer`, but transform the data after `responseType` resolved.\n\n```js\nconst baseRequest = new Request({\n  responseType: \"json\",\n  responseDataTransformer(json) {\n    /* \u003c-- responseDataTransformer */\n    if (json) {\n      json.fullName = `${json.firstName} ${json.familyName}`;\n    }\n    return json;\n  }\n});\n/* ... */\n```\n\n### New `errorTransformer` option\n\nTransform error or rejection.\n\n```js\nconst baseRequest = new Request({\n  errorTransformer(error) {\n    /* \u003c-- errorTransformer */\n    if (error.name === \"Abort\") {\n      console.warn(\"Fetch aborted\");\n    }\n    return error;\n  }\n});\n/* ... */\n```\n\n## Contributing\n\nPlease [checkout CONTRIBUTING.md](/CONTRIBUTING.md)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcap32%2Ffetch-extra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcap32%2Ffetch-extra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcap32%2Ffetch-extra/lists"}