{"id":13468499,"url":"https://github.com/infinitered/apisauce","last_synced_at":"2025-05-14T22:06:47.179Z","repository":{"id":37450971,"uuid":"55873922","full_name":"infinitered/apisauce","owner":"infinitered","description":"Axios + standardized errors + request/response transforms.","archived":false,"fork":false,"pushed_at":"2025-03-19T22:29:07.000Z","size":383,"stargazers_count":2820,"open_issues_count":41,"forks_count":187,"subscribers_count":35,"default_branch":"master","last_synced_at":"2025-05-07T21:59:49.978Z","etag":null,"topics":["api","axios","promise","react-native","reactjs"],"latest_commit_sha":null,"homepage":"","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/infinitered.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2016-04-10T00:44:42.000Z","updated_at":"2025-05-07T02:07:35.000Z","dependencies_parsed_at":"2023-02-09T16:45:29.454Z","dependency_job_id":"5e6aac0e-b145-49ad-98b9-0eff70ee9daa","html_url":"https://github.com/infinitered/apisauce","commit_stats":{"total_commits":254,"total_committers":45,"mean_commits":5.644444444444445,"dds":"0.42913385826771655","last_synced_commit":"ca8ab35bab4f1f1c2be77050a61d87fda6a90a25"},"previous_names":["skellock/apisauce"],"tags_count":49,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fapisauce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fapisauce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fapisauce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infinitered%2Fapisauce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/infinitered","download_url":"https://codeload.github.com/infinitered/apisauce/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253002856,"owners_count":21838640,"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":["api","axios","promise","react-native","reactjs"],"created_at":"2024-07-31T15:01:12.315Z","updated_at":"2025-05-14T22:06:42.157Z","avatar_url":"https://github.com/infinitered.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Apisauce\n\n```\n(Ring ring ring)\n\u003c Hello?\n\u003e Hi, can I speak to JSON API.\n\u003c Speaking.\n\u003e Hi, it's me JavaScript.  Look, we need to talk.\n\u003c Now is not a good time...\n\u003e Wait, I just wanted to say, sorry.\n\u003c ...\n```\n\nTalking to APIs doesn't have to be awkward anymore.\n\n[![npm module](https://badge.fury.io/js/apisauce.svg)](https://www.npmjs.org/package/apisauce)\n\n# Features\n\n- low-fat wrapper for the amazing `axios` http client library\n- all responses follow the same flow: success and failure alike\n- responses have a `problem` property to help guide exception flow\n- attach functions that get called each request\n- attach functions that change all request or response data\n- detects connection issues (on React Native)\n\n# Installing\n\n`npm i apisauce --save` or `yarn add apisauce`\n\n- Depends on `axios`.\n- Compatible with ES5.\n- Built with TypeScript.\n- Supports Node, the browser, and React Native.\n\n# Quick Start\n\n```js\n// showLastCommitMessageForThisLibrary.js\nimport { create } from 'apisauce'\n\n// define the api\nconst api = create({\n  baseURL: 'https://api.github.com',\n  headers: { Accept: 'application/vnd.github.v3+json' },\n})\n\n// start making calls\napi\n  .get('/repos/skellock/apisauce/commits')\n  .then(response =\u003e response.data[0].commit.message)\n  .then(console.log)\n\n// customizing headers per-request\napi.post('/users', { name: 'steve' }, { headers: { 'x-gigawatts': '1.21' } })\n```\n\nSee the examples folder for more code.\n\n# Documentation\n\n## Create an API\n\nYou create an api by calling `.create()` and passing in a configuration object.\n\n```js\nconst api = create({ baseURL: 'https://api.github.com' })\n```\n\nThe only required property is `baseURL` and it should be the starting point for\nyour API. It can contain a sub-path and a port as well.\n\n```js\nconst api = create({ baseURL: 'https://example.com/api/v3' })\n```\n\nHTTP request headers for all requests can be included as well.\n\n```js\nconst api = create({\n  baseURL: '...',\n  headers: {\n    'X-API-KEY': '123',\n    'X-MARKS-THE-SPOT': 'yarrrrr',\n  },\n})\n```\n\nDefault timeouts can be applied too:\n\n```js\nconst api = create({ baseURL: '...', timeout: 30000 }) // 30 seconds\n```\n\nYou can also pass an already created axios instance\n\n```js\nimport axios from 'axios'\nimport { create } from 'apisauce'\n\nconst customAxiosInstance = axios.create({ baseURL: 'https://example.com/api/v3' })\n\nconst apisauceInstance = create({ axiosInstance: customAxiosInstance })\n```\n\n## Calling The API\n\nWith your fresh `api`, you can now call it like this:\n\n```js\napi.get('/repos/skellock/apisauce/commits')\napi.head('/me')\napi.delete('/users/69')\napi.post('/todos', { note: 'jump around' }, { headers: { 'x-ray': 'machine' } })\napi.patch('/servers/1', { live: false })\napi.put('/servers/1', { live: true })\napi.link('/images/my_dog.jpg', {}, { headers: { Link: '\u003chttp://example.com/profiles/joe\u003e; rel=\"tag\"' } })\napi.unlink('/images/my_dog.jpg', {}, { headers: { Link: '\u003chttp://example.com/profiles/joe\u003e; rel=\"tag\"' } })\napi.any({ method: 'GET', url: '/product', params: { id: 1 } })\n```\n\n`get`, `head`, `delete`, `link` and `unlink` accept 3 parameters:\n\n- url - the relative path to the API (required)\n- params - Object - query string variables (optional)\n- axiosConfig - Object - config passed along to the `axios` request (optional)\n\n`post`, `put`, and `patch` accept 3 different parameters:\n\n- url - the relative path to the API (required)\n- data - Object - the object jumping the wire\n- axiosConfig - Object - config passed along to the `axios` request (optional)\n\n`any` only accept one parameter\n\n- config - Object - config passed along to the `axios` request, this object same as `axiosConfig`\n\n## Responses\n\nThe responses are promise-based, so you'll need to handle things in a\n`.then()` function.\n\nThe promised is always resolved with a `response` object.\n\nEven if there was a problem with the request! This is one of the goals of\nthis library. It ensures sane calling code without having to handle `.catch`\nand have 2 separate flows.\n\nA response will always have these 2 properties:\n\n```\nok      - Boolean - True if the status code is in the 200's; false otherwise.\nproblem - String  - One of 6 different values (see below - problem codes)\n```\n\nIf the request made it to the server and got a response of any kind, response\nwill also have these properties:\n\n```\ndata     - Object - this is probably the thing you're after.\nstatus   - Number - the HTTP response code\nheaders  - Object - the HTTP response headers\nconfig   - Object - the `axios` config object used to make the request\nduration - Number - the number of milliseconds it took to run this request\n```\n\nSometimes on different platforms you need access to the original axios error\nthat was thrown:\n\n```\noriginalError - Error - the error that axios threw in case you need more info\n```\n\n## Changing Base URL\n\nYou can change the URL your api is connecting to.\n\n```js\napi.setBaseURL('https://some.other.place.com/api/v100')\nconsole.log(`omg i am now at ${api.getBaseURL()}`)\n```\n\n## Changing Headers\n\nOnce you've created your api, you're able to change HTTP requests by\ncalling `setHeader` or `setHeaders` on the api. These stay with the api instance, so you can just set ['em and forget 'em](https://gitter.im/infinitered/ignite?at=582e57563f3946057acd2f84).\n\n```js\napi.setHeader('Authorization', 'the new token goes here')\napi.setHeaders({\n  Authorization: 'token',\n  'X-Even-More': 'hawtness',\n})\n```\n\n## Adding Monitors\n\nMonitors are functions you can attach to the API which will be called\nwhen any request is made. You can use it to do things like:\n\n- check for headers and record values\n- determine if you need to trigger other parts of your code\n- measure performance of API calls\n- perform logging\n\nMonitors are run just before the promise is resolved. You get an\nearly sneak peak at what will come back.\n\nYou cannot change anything, just look.\n\nHere's a sample monitor:\n\n```js\nconst naviMonitor = response =\u003e console.log('hey!  listen! ', response)\napi.addMonitor(naviMonitor)\n```\n\nAny exceptions that you trigger in your monitor will not affect the flow\nof the api request.\n\n```js\napi.addMonitor(response =\u003e this.kaboom())\n```\n\nInternally, each monitor callback is surrounded by an oppressive `try/catch`\nblock.\n\nRemember. Safety first!\n\n## Adding Transforms\n\nIn addition to monitoring, you can change every request or response globally.\n\nThis can be useful if you would like to:\n\n- fix an api response\n- add/edit/delete query string variables for all requests\n- change outbound headers without changing everywhere in your app\n\nUnlike monitors, exceptions are not swallowed. They will bring down the stack, so be careful!\n\n### Response Transforms\n\nFor responses, you're provided an object with these properties.\n\n- `data` - the object originally from the server that you might wanna mess with\n- `duration` - the number of milliseconds\n- `problem` - the problem code (see the bottom for the list)\n- `ok` - true or false\n- `status` - the HTTP status code\n- `headers` - the HTTP response headers\n- `config` - the underlying axios config for the request\n\nData is the only option changeable.\n\n```js\napi.addResponseTransform(response =\u003e {\n  const badluck = Math.floor(Math.random() * 10) === 0\n  if (badluck) {\n    // just mutate the data to what you want.\n    response.data.doorsOpen = false\n    response.data.message = 'I cannot let you do that.'\n  }\n})\n```\n\nOr make it async:\n\n```js\napi.addAsyncResponseTransform(async response =\u003e {\n  const something = await AsyncStorage.load('something')\n  if (something) {\n    // just mutate the data to what you want.\n    response.data.doorsOpen = false\n    response.data.message = 'I cannot let you do that.'\n  }\n})\n```\n\n### Request Transforms\n\nFor requests, you are given a `request` object. Mutate anything in here to change anything about the request.\n\nThe object passed in has these properties:\n\n- `data` - the object being passed up to the server\n- `method` - the HTTP verb\n- `url` - the url we're hitting\n- `headers` - the request headers\n- `params` - the request params for `get`, `delete`, `head`, `link`, `unlink`\n\nRequest transforms can be a function:\n\n```js\napi.addRequestTransform(request =\u003e {\n  request.headers['X-Request-Transform'] = 'Changing Stuff!'\n  request.params['page'] = 42\n  delete request.params.secure\n  request.url = request.url.replace(/\\/v1\\//, '/v2/')\n  if (request.data.password \u0026\u0026 request.data.password === 'password') {\n    request.data.username = `${request.data.username} is secure!`\n  }\n})\n```\n\nAnd you can also add an async version for use with Promises or `async/await`. When you resolve\nyour promise, ensure you pass the request along.\n\n```js\napi.addAsyncRequestTransform(request =\u003e {\n  return new Promise(resolve =\u003e setTimeout(resolve, 2000))\n})\n```\n\n```js\napi.addAsyncRequestTransform(request =\u003e async () =\u003e {\n  await AsyncStorage.load('something')\n})\n```\n\nThis is great if you need to fetch an API key from storage for example.\n\nMultiple async transforms will be run one at a time in succession, not parallel.\n\n# Using Async/Await\n\nIf you're more of a `stage-0` kinda person, you can use it like this:\n\n```js\nconst api = create({ baseURL: '...' })\nconst response = await api.get('/slowest/site/on/the/net')\nconsole.log(response.ok) // yay!\n```\n\n# Cancel Request\n\n```js\nimport { CancelToken } from 'apisauce'\n\nconst source = CancelToken.source()\nconst api = create({ baseURL: 'github.com' })\napi.get('/users', {}, { cancelToken: source.token })\n\n// To cancel request\nsource.cancel()\n```\n\n# Problem Codes\n\nThe `problem` property on responses is filled with the best\nguess on where the problem lies. You can use a switch to\ncheck the problem. The values are exposed as `CONSTANTS`\nhanging on your built API.\n\n```\nConstant        VALUE               Status Code   Explanation\n----------------------------------------------------------------------------------------\nNONE             null               200-299       No problems.\nCLIENT_ERROR     'CLIENT_ERROR'     400-499       Any non-specific 400 series error.\nSERVER_ERROR     'SERVER_ERROR'     500-599       Any 500 series error.\nTIMEOUT_ERROR    'TIMEOUT_ERROR'    ---           Server didn't respond in time.\nCONNECTION_ERROR 'CONNECTION_ERROR' ---           Server not available, bad dns.\nNETWORK_ERROR    'NETWORK_ERROR'    ---           Network not available.\nCANCEL_ERROR     'CANCEL_ERROR'     ---           Request has been cancelled. Only possible if `cancelToken` is provided in config, see axios `Cancellation`.\n```\n\nWhich problem is chosen will be picked by walking down the list.\n\n# Mocking with axios-mock-adapter (or other libraries)\n\nA common testing pattern is to use `axios-mock-adapter` to mock axios and respond with stubbed data. These libraries mock a specific instance of axios, and don't globally intercept all instances of axios. When using a mocking library like this, it's important to make sure to pass the same axios instance into the mock adapter.\n\nHere is an example code from axios_mock, modified to work with Apisauce:\n\n```diff\nimport apisauce from 'apisauce'\nimport MockAdapter from 'axios-mock-adapter'\n\ntest('mock adapter', async () =\u003e {\n  const api = apisauce.create(\"https://api.github.com\")\n- const mock = new MockAdapter(axios)\n+ const mock = new MockAdapter(api.axiosInstance)\n  mock.onGet(\"/repos/skellock/apisauce/commits\").reply(200, {\n    commits: [{ id: 1, sha: \"aef849923444\" }],\n  });\n\n  const response = await api..get('/repos/skellock/apisauce/commits')\n  expect(response.data[0].sha).toEqual\"aef849923444\")\n})\n```\n\n# Contributing\n\nBugs? Comments? Features? PRs and Issues happily welcomed! Make sure to check out our [contributing guide](./github/CONTRIBUTING.md) to get started!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfinitered%2Fapisauce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finfinitered%2Fapisauce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfinitered%2Fapisauce/lists"}