{"id":21404720,"url":"https://github.com/eightyfive/fetch-run","last_synced_at":"2026-02-27T01:01:41.639Z","repository":{"id":40260772,"uuid":"156203118","full_name":"eightyfive/fetch-run","owner":"eightyfive","description":"Fetch middleware for the modern minimalist","archived":false,"fork":false,"pushed_at":"2025-11-14T05:50:46.000Z","size":585,"stargazers_count":11,"open_issues_count":3,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-14T07:24:02.788Z","etag":null,"topics":["api","fetch","middleware","onion","stack"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/eightyfive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-11-05T10:55:58.000Z","updated_at":"2025-02-14T20:40:17.000Z","dependencies_parsed_at":"2025-07-13T23:30:59.485Z","dependency_job_id":"8c5e540b-461d-4826-a56e-ecb6cad8af0e","html_url":"https://github.com/eightyfive/fetch-run","commit_stats":null,"previous_names":[],"tags_count":64,"template":false,"template_full_name":null,"purl":"pkg:github/eightyfive/fetch-run","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eightyfive%2Ffetch-run","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eightyfive%2Ffetch-run/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eightyfive%2Ffetch-run/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eightyfive%2Ffetch-run/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eightyfive","download_url":"https://codeload.github.com/eightyfive/fetch-run/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eightyfive%2Ffetch-run/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29879896,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T23:51:21.483Z","status":"ssl_error","status_checked_at":"2026-02-26T23:50:46.793Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["api","fetch","middleware","onion","stack"],"created_at":"2024-11-22T16:17:45.131Z","updated_at":"2026-02-27T01:01:39.595Z","avatar_url":"https://github.com/eightyfive.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `fetch-run`\n\nFetch middleware for the modern minimalist.\n\n- [Install](#install)\n- [Usage](#usage)\n- [Middlewares](#middlewares)\n  - [Before/after concept](#beforeafter-concept)\n  - [Execution order (LIFO)](#execution-order-lifo)\n- [`Http` flavour](#http-flavour)\n- [API](#api)\n- [Included middleware](#included-middleware)\n  - [HTTP error](#http-error)\n  - [HTTP error (Metro bundler)](#http-error-metro-bundler)\n  - [Log requests \u0026 responses (DEV)](#log-requests--responses-dev)\n  - [`XSRF-TOKEN` cookie (CSRF)](#xsrf-token-cookie-csrf)\n\n## Install\n\n```\nyarn add fetch-run\n```\n\n## Usage\n\n```ts\nimport { Api } from 'fetch-run';\nimport * as uses from 'fetch-run/use';\n\nconst api = Api.create('https://example.org/api/v1');\n\nif (__DEV__) {\n  api.use(uses.logger);\n}\n\napi.use(uses.error);\n\n// Later in app\ntype LoginRes = { token: string };\ntype LoginReq = { email: string; password };\ntype User = { id: number; name: string };\n\napi.post\u003cLoginRes, LoginReq\u003e('login', data);\n\napi.get\u003cUser\u003e(`users/${id}`).then((user) =\u003e {});\n\napi.search\u003cUser[]\u003e('users', { firstName: 'John' }).then((users) =\u003e {});\n```\n\n## Middlewares\n\nA simple implementation of the middleware pattern. It allows you to modify the [Request object](https://developer.mozilla.org/en-US/docs/Web/API/Request) before your API call and use the [Response object](https://developer.mozilla.org/en-US/docs/Web/API/Response) right after receiving the response from the server.\n\nHere are some examples/implementations of the middleware pattern:\n\n- [Using Express middleware](https://expressjs.com/en/guide/using-middleware.html)\n- [Middleware - Laravel](https://laravel.com/docs/5.7/middleware)\n- [Middleware - Redux](https://redux.js.org/advanced/middleware)\n\nA good way to visualize the middleware pattern is to think of the Request/Response lifecycle [as an onion](https://www.google.com/search?q=middleware+onion\u0026tbm=isch). Every middleware added to the stack being a new onion layer on top of the previous one.\n\nEvery middleware takes a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) in and _must_ give a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) out.\n\n```ts\ntype Layer = (req: Request) =\u003e Promise\u003cResponse\u003e;\n\ntype Middleware = (next: Layer) =\u003e Layer;\n\n// src/http/my-middleware.ts\n\nexport const myMiddleware: Middleware =\n  (next: Layer) =\u003e async (req: Request) =\u003e {\n    // Before\n\n    const res: Response = await next(req);\n\n    // After\n\n    return res; // Response\n  };\n```\n\n### Before/after concept\n\nLet's write a simple middleware that remembers an \"access token\" and sets a \"Bearer header\" on the next [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) once available.\n\n```js\n// src/http/access-token.js\n\nlet accessToken;\n\nexport default (next) =\u003e async (req) =\u003e {\n  //\n  // BEFORE\n  // Modify/Use Request\n  //\n\n  if (accessToken) {\n    req.headers.set('Authorization', `Bearer ${accessToken}`);\n  }\n\n  const res = await next(req);\n\n  //\n  // AFTER\n  // Modify/Use Response\n  //\n\n  if (res.access_token) {\n    accessToken = res.access_token;\n  }\n\n  return res;\n};\n```\n\n### Execution order (LIFO)\n\nSince everything is a middleware, the _order of execution_ is important.\n\nMiddlewares are executed in [LIFO order](https://en.wikipedia.org/wiki/FIFO_and_LIFO_accounting#LIFO) (\"Last In, First Out\").\n\nEverytime you push a new middleware to the stack, it is added as a new [onion layer](https://www.google.com/search?q=middleware+onion\u0026tbm=isch) on top of all existing ones.\n\n#### Example\n\n```js\napi.use(A);\napi.use(B);\n```\n\nExecution order:\n\n1. `B` \"Before\" logic\n2. `A` \"Before\" logic\n3. (actual `fetch` call)\n4. `A` \"After\" logic\n5. `B` \"After\" logic\n\n_Note_: `B` is the most outer layer of the [onion](https://www.google.com/search?q=middleware+onion\u0026tbm=isch).\n\n## `Http` flavour\n\nThe library also exports an `Http` flavour that does not transform the [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) to JSON.\n\n```ts\nimport { Http } from 'fetch-run';\n\nconst http = new Http('https://example.org');\n\nhttp.use(error);\n\nhttp.get('index.html').then((res: Response) =\u003e {\n  // https://developer.mozilla.org/en-US/docs/Web/API/Response\n  res.blob();\n  res.formData();\n  res.json();\n  res.text();\n  // ...\n});\n```\n\n## API\n\n### `constructor(baseUrl: string, defaultOptions?: RequestInit)`\n\nCreates a new instance of `Api` or `Http`.\n\n```ts\nconst api = new Api('', { credentials: 'include' });\n\nconst http = new Http('https://example.org', {\n  mode: 'no-cors',\n  headers: { 'X-Foo': 'Bar' },\n});\n```\n\n### `static create(baseUrl?: string, defaultOptions?: RequestInit)`\n\nAlternative \u0026 convenient way for creating an instance.\n\n```ts\nconst api = Api.create('', { credentials: 'include' });\n\nconst http = Http.create('https://example.org', {\n  mode: 'no-cors',\n  headers: { 'X-Foo': 'Bar' },\n});\n```\n\n#### Note\n\n`Api.create` will add the following default headers:\n\n```json\n{\n  \"Accept\": \"application/json\",\n  \"Content-Type\": \"application/json\"\n}\n```\n\n`new Api`, `new Http` \u0026 `Http.create` do not.\n\n### `use(middleware: Middleware)`\n\nAdds a middleware to the stack. See [Middlewares](https://github.com/eightyfive/fetch-run#middlewares) and [Execution order (LIFO)](https://github.com/eightyfive/fetch-run#execution-order-lifo) for more information.\n\n```ts\ntype Layer = (req: Request) =\u003e Promise\u003cResponse\u003e;\ntype Middleware = (next: Layer) =\u003e Layer;\n```\n\n### `get\u003cRes\u003e(path: string, options?: RequestInit)`\n\nPerforms a `GET` request. If you need to pass query parameters to the URL, use `search` instead.\n\n### `search\u003cRes\u003e(path: string, query: object, options?: RequestInit)`\n\nPerforms a `GET` request with additional query parameters passed in URL.\n\n### `post\u003cRes, Req extends BodyData\u003e(path: string, data?: Req, options?: RequestInit)`\n\nPerforms a `POST` request.\n\n```ts\ntype BodyData = FormData | object | void;\n```\n\n### `put\u003cRes, Req extends BodyData\u003e(path: string, data?: Req, options?: RequestInit)`\n\nPerforms a `PUT` request.\n\n### `patch\u003cRes, Req extends BodyData\u003e(path: string, data?: Req, options?: RequestInit)`\n\nPerforms a `PATCH` request.\n\n### `delete(path: string, options?: RequestInit)`\n\nPerforms a `DELETE` request.\n\n### `options?: RequestInit`\n\nAll `options` are merged with the default options (`constructor`) and passed down to the [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object.\n\n## Included middleware\n\n### HTTP Error\n\n- Catch HTTP responses with error status code (`\u003c 200 || \u003e= 300` – a.k.a. [`response.ok`](https://developer.mozilla.org/en-US/docs/Web/API/Response/ok))\n- Create a custom [`err: HTTPError`](https://github.com/eightyfive/fetch-run/blob/master/src/error.ts)\n- Set `err.code = res.status`\n- Set `err.message = res.statusText`\n- Set `err.request = req`\n- Set `err.response = res`\n- Throw `HTTPError`\n\n```js\nimport { error } from 'fetch-run/use';\n\napi.use(error);\n```\n\nLater in app:\n\n```js\nimport { HTTPError } from 'fetch-run';\n\ntry {\n  api.updateUser(123, { name: 'Tyron' });\n} catch (err) {\n  if (err instanceof HTTPError) {\n    err.response.json(); //...\n  } else {\n    throw err;\n  }\n}\n```\n\n#### Note (order of execution)\n\nAll middlewares registered _after_ the `error` middleware, will not be executed (`error` middleware throws).\n\nThis is why, for example, you need to register the `logger` middleware first, so it can log `req` \u0026 `res` before the error is thrown.\n\n### HTTP Error (Metro bundler)\n\nThe Metro bundler (React Native) fails with `ENOENT` error when throwing a [custom `Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#custom_error_types):\n\n```\nError: ENOENT: no such file or directory, open '\u003capp-root\u003e/HTTPError@http:/127.0.0.1:19000/node_modules/expo/AppEntry.bundle?platform=ios\u0026dev=true\u0026hot=false'\n```\n\nThis is why we need to throw a \"normal\" `Error` and unfortunately not the custom `HTTPError` itself (yet?).\n\nThis prevents the use of `instanceof HTTPError` + requires to [assert the type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) when using Typescript:\n\n```ts\nimport { HTTPError } from 'fetch-run';\n\ntry {\n  // ...\n} catch (err: Error) {\n  // if (err instanceof HTTPError) // Cannot...\n  if (err.name === 'HTTPError') {\n    // Assert type...\n    (err as HTTPError).response.json(); // ...\n  }\n}\n```\n\nSee [source code](https://github.com/eightyfive/fetch-run/blob/master/src/use/error-metro.ts) for more details.\n\n```js\nimport { errorMetro } from 'fetch-run/use';\n\napi.use(errorMetro);\n```\n\n### Log requests \u0026 responses (DEV)\n\nA simple `Request` \u0026 `Response` console logger for when you don't need (yet) the full [Debug Remote JS](https://docs.expo.dev/workflow/debugging/) capabilities.\n\n```js\nimport { logger } from 'fetch-run/use';\n\nif (__DEV__) {\n  api.use(logger);\n}\n\n// Note: To register before `error` middleware (throws)\n// api.use(error)\n```\n\n[Source code](https://github.com/eightyfive/fetch-run/blob/master/src/use/logger.ts)\n\n\u003cimg src=\"images/console-log.png\" /\u003e\n\n\u003cimg src=\"images/console-error.png\" /\u003e\n\n### `XSRF-TOKEN` cookie (CSRF)\n\nFor example when used with [Laravel Sanctum](https://laravel.com/docs/9.x/sanctum#csrf-protection).\n\n- Get `XSRF-TOKEN` cookie value\n- Set `X-XSRF-TOKEN` header\n\n```js\nimport { xsrf } from 'fetch-run/use';\n\napi.use(xsrf);\n```\n\n[Source code](https://github.com/eightyfive/fetch-run/blob/master/src/use/xsrf.ts)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feightyfive%2Ffetch-run","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feightyfive%2Ffetch-run","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feightyfive%2Ffetch-run/lists"}