{"id":22283114,"url":"https://github.com/lchrennew/es-fetch-api","last_synced_at":"2025-07-28T21:32:26.373Z","repository":{"id":57226935,"uuid":"455633152","full_name":"lchrennew/es-fetch-api","owner":"lchrennew","description":"Powerful extensible HTTP client for both node.js and browser.","archived":false,"fork":false,"pushed_at":"2024-04-19T07:06:17.000Z","size":56,"stargazers_count":18,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-29T23:35:11.444Z","etag":null,"topics":["fetch","http","nodejs","react","vue"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/es-fetch-api","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lchrennew.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-02-04T17:15:51.000Z","updated_at":"2023-07-06T19:25:24.000Z","dependencies_parsed_at":"2024-04-19T08:24:50.800Z","dependency_job_id":"a6683732-c8d9-40a7-b174-f9ff3609cf5d","html_url":"https://github.com/lchrennew/es-fetch-api","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lchrennew%2Fes-fetch-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lchrennew%2Fes-fetch-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lchrennew%2Fes-fetch-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lchrennew%2Fes-fetch-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lchrennew","download_url":"https://codeload.github.com/lchrennew/es-fetch-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227958653,"owners_count":17847444,"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":["fetch","http","nodejs","react","vue"],"created_at":"2024-12-03T16:38:40.908Z","updated_at":"2024-12-03T16:38:41.799Z","avatar_url":"https://github.com/lchrennew.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ES-Fetch-API\n\n[中文](./README_CHS.md)  | English\n\nVery very very powerful, extensible http client for both node.js and browser.\n\n![NPM](https://img.shields.io/npm/l/es-fetch-api)\n![npm](https://img.shields.io/npm/v/es-fetch-api)\n![GitHub package.json version](https://img.shields.io/github/package-json/v/lchrennew/es-fetch-api)\n![GitHub file size in bytes](https://img.shields.io/github/size/lchrennew/es-fetch-api/fetch.js)\n![GitHub issues](https://img.shields.io/github/issues-raw/lchrennew/es-fetch-api)\n![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/lchrennew/es-fetch-api)\n[![codebeat badge](https://codebeat.co/badges/8987f803-3d78-4959-89bd-6a374f7f3472)](https://codebeat.co/projects/github-com-lchrennew-es-fetch-api-main)\n[![CodeQL](https://github.com/lchrennew/es-fetch-api/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/lchrennew/es-fetch-api/actions/workflows/codeql-analysis.yml)\n\n## Why should you use ES-Fetch API?\n\nStill using `axios`? `ES-Fetch-API` creates sunny world for you.\n\n### i. It's extremely light-weight and built on the native fetch API.\n\nComparing to axios which is ~400kB, `es-fetch-api` is just ~6kB. Because es-fetch-api is designed for native fetch API\ncompatible environments.\n\nReferences:\n\n1. [fetch API on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#browser_compatibility)\n2. [fetch API on whatwg.org](https://fetch.spec.whatwg.org/)\n\n### ii. Enables maximized readability, extensibility, maintainability and minimized complexity.\n\n#### 1. The simplest example\n\nExpected request:\n\n```http\nGET http://yourdomain.com/api/v1/user?id=12345\n```\n\nUsing axios:\n\n```javascript\nimport axios from 'axios'\n\n// unneccessarily indicate that 'http://yourdomain.com/api/v1' means baseURL\nconst apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })\n\n// unneccessarily indicate that `/user` means url\nconst getUser = async id =\u003e await apiV1.get({ url: `/user`, params: { id } })\n\nconst response = await getUser(12345)\n```\n\nUsing es-fetch-api, great readability:\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { query } from 'es-fetch-api/middelwares/query.js'\n\n// without mincing words\nconst apiV1 = getApi('http://yourdomain.com/api/v1')\n\nconst getUser = async id =\u003e await apiV1(`user`, query({ id }))\n\nconst response = await getUser(12345)\n```\n\n#### 2. More complicated example (using built-in middlewares)\n\nExpected request:\n\n```\nPOST http://yourdomain.com/api/v1/user/\nContent-Type: application/json\n\n{\"firstName\":\"Fred\",\"lastName\":\"Flintstone\"}\n```\n\nUsing axios:\n\n```javascript\nimport axios from 'axios'\n\nconst apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })\n\n// which format is used to post data?\nconst createUser = async user =\u003e await apiV1.post(`/user`, user)\n\nconst resposne = await createUser({\n    firstName: 'Chun',\n    lastName: 'Li'\n})\n```\n\nUsing es-fetch-api, better readability:\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { json } from 'es-fetch-api/middelwares/body.js'\nimport { POST } from 'es-fetch-api/middelwares/methods.js'\n\nconst apiV1 = getApi('http://yourdomain.com/api/v1')\n\n// read what you see infomation losslessly \nconst createUser = async user =\u003e await apiV1(`user`, POST, json(user))\n\nconst resposne = await createUser({\n    firstName: 'Chun',\n    lastName: 'Li'\n})\n```\n\n#### 3. Create custom middleware to extend your code while keeping better readability.\n\nExpected request:\n\n```\nPOST http://yourdomain.com/api/v1/user/\nContent-Type: application/json\nAuhorization: Token ********\nX-Timestamp: ##########\n\n{\"firstName\":\"Fred\",\"lastName\":\"Flintstone\"}\n```\n\nUsing axios:\n\n```javascript\nimport axios from 'axios'\nimport { getToken } from 'token-helper'\n\n// easy to read? it's hard to understand they return headers.\nconst useToken = async () =\u003e ({ 'Authorization': `Token ${await getToken()}` })\nconst useTimestamp = async () =\u003e ({ 'X-Timestamp': Date.now() })\n\nconst apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })\n\n// easy to read? Maybe or not, but too long winded to maintain.\nconst createUser = async user =\u003e await apiV1.post({\n    url: `/user`,\n    data: user,\n    headers: { ...await useToken(), ...await useTimestamp() }\n})\n\nconst resposne = await createUser({\n    firstName: 'Chun',\n    lastName: 'Li'\n})\n```\n\nUsing es-fetch-api, better readability, better maintainability:\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { json } from 'es-fetch-api/middelwares/body.js'\nimport { POST } from 'es-fetch-api/middelwares/methods.js'\nimport { getToken } from 'token-helper'\n\n// read what you see\nconst useToken = async (ctx, next) =\u003e {\n    ctx.header('Authorization', `Token ${await getToken()}`)\n    return await next()\n}\nconst useTimestamp = async (ctx, next) =\u003e {\n    ctx.header('X-Timestamp', Date.now())\n    return await next()\n}\n\nconst apiV1 = getApi('http://yourdomain.com/api/v1')\n\n// read what you see infomation-losslessly \nconst createUser = async user =\u003e await apiV1(`user`, POST, json(user), useToken, useTimestamp)\n\nconst resposne = await createUser({\n    firstName: 'Chun',\n    lastName: 'Li'\n})\n```\n\n#### 4. To use custom middlewares for every invocation.\n\nUsing axios:\n\n```javascript\nimport axios from 'axios'\nimport { getToken } from 'token-helper'\n\nconst useToken = async () =\u003e ({ 'Authorization': `Token ${await getToken()}` })\nconst useTimestamp = async () =\u003e ({ 'X-Timestamp': Date.now() })\n\n// headers is static, especially the X-Timestamp. Easy to maintain? No!\nconst apiV1 = axios.create({\n    baseURL: 'http://yourdomain.com/api/v1',\n    headers: { ...await useToken(), ...await useTimestamp() }\n})\n\nconst createUser = async user =\u003e await apiV1.post({ url: `/user`, data: user, })\nconst getUser = async id =\u003e await apiV1.get({ url: `/user`, params: { id } })\n\n```\n\nUsing es-fetch-api, better readability, better maintainability:\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { json } from 'es-fetch-api/middelwares/body.js'\nimport { POST } from 'es-fetch-api/middelwares/methods.js'\nimport { getToken } from 'token-helper'\n\nconst useToken = async (ctx, next) =\u003e {\n    ctx.header('Authorization', `Token ${await getToken()}`)\n    return await next()\n}\nconst useTimestamp = async (ctx, next) =\u003e {\n    ctx.header('X-Timestamp', Date.now())\n    return await next()\n}\n\n// Just append the middlewares, so easy.\nconst apiV1 = (...args) =\u003e getApi('http://yourdomain.com/api/v1')(...args, useToken, useTimestamp)\n\nconst createUser = async user =\u003e await apiV1(`user`, POST, json(user))\n```\n\n#### 5. Process response.\n\nFor instance with the getUser function.\n\nWhen the user exists, the response should be:\n\n```text\nStatus: 200 OK\nContent-Type: application/json\nBody: {ok: true, data: {\"firstName\": \"Chun\", \"lastName\": \"Li\"}}\n```\n\nWhen the user doesn't exist, the resposne should be:\n\n```text\nStatus: 404 NotFound\nContent-Type: application/json\nBody: {ok: false, message: 'User doesn't exist.'}\n```\n\nUsing axios:\n\n```javascript\nimport axios from 'axios'\n\nconst apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })\nconst getUser = async id =\u003e {\n    try {\n        const response = await apiV1.get({ url: `/user`, params: { id } })\n        console.log(response.status, response.statusText)\n        // So many data and error, make me confused...don't forget write the .data after the response :)\n        const { data } = response.data\n        return data\n    } catch (error) {\n        // which error is the error i want to use?\n        console.log(error.response.data.message ?? error.message)\n    }\n}\n```\n\nUsing es-fetch-api, great readability:\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { query } from 'es-fetch-api/middelwares/query.js'\n\nconst apiV1 = getApi('http://yourdomain.com/api/v1')\n\nconst getUser = async id =\u003e {\n    try {\n        const response = await apiV1(`user`, query({ id }))\n        console.log(response.status, response.statusText)\n        const { ok, data, message } = await response.json() // read what you see\n        if (!ok) throw { message }  // throw the error as you will\n        return data\n    } catch (error) {\n        console.log(error.message)\n    }\n}\n```\n\n#### 6. Process responses in a unified way\n\nUsing axios:\n\n```javascript\nimport axios from 'axios'\n\n// can you understand it? \n// There seems no way to process errors in a unified way?\nconst apiV1 = axios.create({ baseURL: 'http://yourdomain.com/api/v1' })\n\nconst getOne = async config =\u003e {\n    try {\n        const resposne = await apiV1(config)\n        console.log(response.status, response.statusText)\n        const { ok, data, message } = response.data\n        return data\n    } catch (error) {\n        console.log(error.response.data.message ?? error.message)\n    }\n}\n```\n\nUsing es-fetch-api, great readability:\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { query } from 'es-fetch-api/middelwares/query.js'\n\nconst apiV1 = getApi('http://yourdomain.com/api/v1')\n\nconst getOne = async (...args) =\u003e {\n    try {\n        const resposne = await apiV1(...args, useToken) // you can append custom middlewares here.\n        console.log(response.status, response.statusText)\n        const { ok, data, message } = await response.json() // read what you see\n        if (!ok) throw { message }  // throw the error as you will\n        return data\n    } catch (error) {\n        console.log(error.message ?? error)\n    }\n}\n\n// getOne is the unified way to process every response. You could also write other logics such as getList\n// read what you see\nconst getUser = async id =\u003e getOne(`user`, query({ id }))\n```\n\n### One word reason\n\nIn es-fetch-api, each api invocation is a middlewares-chain, which means everything is extensible without introducing\nmore complexity, no matter you want to process request and response in any unified way or case by case.\n\n## Built-in middlewares\n\n### `method` middleware\n\nThis middleware is used to set HTTP method, it accepts a string parameter for method name to use. If an unsupported\nmethod name is used, an exception will be thrown.\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { method } from 'es-fetch-api/middelwares/methods.js'\n\nconst api = getApi('http://mydomain.com/api')\n\nconst response = api('/', method('DELETE'))\n```\n\n### `method` aliases\n\n`GET`, `POST`, `PUT`, `PATCH` and `DELETE`, these are shorthands for each corresponding `method`.\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { DELETE } from 'es-fetch-api/middelwares/methods.js'\n\nconst api = getApi('http://mydomain.com/api')\n\nconst response = api('/', DELETE)\n```\n\n### `json` middleware\n\nThis middleware is used to declare the HTTP request body is an JSON object.\n\nIt accepts an Object parameter to pass the body object in.\n\nWhen you use this middleware, the `Content-Type: application/json` header will be set automatically.\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { json } from 'es-fetch-api/middelwares/body.js'\nimport { POST } from 'es-fetch-api/middelwares/methods.js'\n\nconst api = getApi('http://mydomain.com/api')\n\nconst response = api('/', POST, json({ hello, world }))\n```\n\n### `query` middleware\n\nThis middleware is used to declare the query string parameters of the request URL.\n\nIt accepts two parameters.\n\n1. an Object, whose keys are the query parameter names and their corresponding value(s) are the query parameter values.\n   If a value is an array with more than one element, then it will be an multi-value parameter.\n2. a Boolean, used to indicate whether each query parameter value should be appended to existed values. By Default,\n   it's `false`.\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { query } from 'es-fetch-api/middelwares/query.js'\n\nconst api = getApi('http://mydomain.com/api?hello=1')\n\napi(query({ hello: 'world' })) // http://mydomain.com/api?hello=world\napi(query({ hello: 'world' }, true)) // http://mydomain.com/api?hello=1\u0026hello=world\napi(query({ hello: [ 'Bing', 'Dwen', 'Dwen' ], world: '2022' })) // http://mydomain.com/api?hello=Bing\u0026hello=Dwen\u0026hello=Dwen\u0026world=2022\n```\n\n### `form` middleware\n\nThis middleware is used to declare the HTTP request body is form data.\n\nIt accepts an Object parameter to pass form data in.\n\nWhen you use this middleware, the `Content-Type: application/x-www-form-urlencoded` header will be set automatically.\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { form } from 'es-fetch-api/middelwares/body.js'\n\nconst api = getApi('http://mydomain.com/api')\n\napi(POST, form({ hello: 'world' })) // hello=world\n```\n\n### `file` middleware\n\nThis middleware is used to declare the HTTP request is uploading files.\n\nIt accepts three parameters:\n\n1. the name of FormData field contains the file\n2. a `File` object\n3. give a filename, by default it's the original filename\n\n### `abortable` middleware\n\nThis middleware injects AbortController::signal into fetch, so that you can abort the request as you wish.\n\nYou can use it to implement manual abort, timeout abort and so on.\n\nWhen the AbortController::abort() is invoked, an exception will be thrown.\n\n```javascript\nimport { getApi } from \"es-fetch-api\";\nimport { abortable } from 'es-fetch-api/middelwares/abortable.js'\n\nconst api = getApi('http://mydomain.com/api')\nconst controller = new AbortController()\nsetTimeout(() =\u003e controller.abort(), 1000)\napi(abortable(controller))\n```\n\n## Middleware development\n\nEach middleware follow the same signature (`Function` and `AsyncFunction` both OK) and finally `return next()`.\n\n```javascript\nconst example = (ctx, next) =\u003e {\n    // TODO: your logic\n    return next()\n}\n```\n\nor\n\n```javascript\nconst example = async (ctx, next) =\u003e {\n    // TODO: your logic\n    return next()\n}\n```\n\n### More about the `ctx`\n\nThe `ctx` is completely same as the [Request](https://fetch.spec.whatwg.org/#request-class), except the `ctx`\nexposes a helper method used to set request headers, see `useToken` middleware example in this document.\n\n## License\n\n[MIT](./LICENSE)\n\n## Translations\n\n- [Chinese](./README_CHS.md)\n- [English](./README.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flchrennew%2Fes-fetch-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flchrennew%2Fes-fetch-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flchrennew%2Fes-fetch-api/lists"}