sh-api-client v3.3.0
api-client
Sharpr JavaScript api client for use in React and Node
Tell me more
Similar to our Angular ApiProvider but more intuitive. Target uses include:
- New React code
- API v4 endpoints written in JavaScript
- API integration testing (v2, v3, v4)
- Single-use scripts for reports or importing data (e.g. queries repo)
Extra features include:
- HTTP errors cause a promise rejection
- You can specify a timeout
- It reads Sharpr's
API-*HTTP headers - Supports caching in milliseconds or as an expression
- You can abort requests that match a certain pattern
- It supports interceptors for request, response, error, timeout, abort
- Using
node-fetchit supports fetching on Node
Table of Contents
- Request API
- How endpoints work
- Response API
- Handling Errors
- Special Methods
- Aborting Requests
- Caching
- Mocking with fetch-mock
- Interceptors
- APIRequest
Request API
Use the following key for the origin of each option:
[f]From window.fetch - docs[S]Custom Sharpr item
const api = require('api-client');
api.head(endpoint, params, options);
api.get(endpoint, params, options);
api.post(endpoint, payload, options);
api.delete(endpoint, payload, options);
api.put(endpoint, payload, options);
api.patch(endpoint, payload, options);
api.request(method, endpoint, params, options);
api.abort(methodStringOrRegex, endpointStringOrRegex);Where:
{String} methodGET, POST, DELETE, etc.[f]{String} endpointThe name of the API endpoint such as /posts/123[S]{Object} [paramsOrData]For GET requests, the query params; otherwise JSON payload[S]{Object} [options]Additional overrides including { headers }{Object} headersAny request headers[f]{Number} timeoutThe number of milliseconds after which to time out (default is 5 minutes)[S]{Boolean} avoidLoadingBarIf true, do not show loading bar (React only)[S]
Returns:
A promise that resolves with ApiResponse or rejects with ApiError.
How endpoints and URLs work
On sharpr.com:
/posts/123Is changed tohttps://sharpr.com/api/v2/posts/123/v3/posts/searchIs changed tohttps://sharpr.com/api/v3/posts/searchhttps://sharprua.com/status/tests.phpIs left alone
When using api-client in Node or fetching from external domains, URLs need to
start with the full domain or else you can set a default using setBaseURL():
api.setBaseURL('https://example.com');Response API
ApiResponse is a custom Sharpr class to make it easier to work with responses.
{Object} requestThe original ApiRequest object that generated this response{String} urlA raw URL to use instead of the endpoint[S]{String} methodGET, POST, DELETE, etc.[f]{Object} searchParamsparams that were serialized into the GET string[k]{Object} headersAny request headers[f]{*} jsonThe JSON payload[f]{Number} timeoutThe number of milliseconds after which to time out[S]{Boolean} avoidLoadingBarIf true, do not show loading bar[S]
{String} typeEither "json", "text" or null depending on response type[S]{*} dataThe decoded response or text[S]{String} endpointThe name of the API endpoint such as /posts/123[S]{Response} rawResponseThe rawfetchresponse[S]{Boolean} okTrue if response is 2xx[f]{Number} statusThe status code[f]{String} statusTextStatus name such as "OK" or "No Content"[f]{Headers} headersHeaders object withget(),has(), etc.{Number} totalThe value ofAPI-Total-Recordsresponse header[S]{Number} sizeThe number of items returned in Request.data[S]{Number} limitThelimitparam that was passed in[S]{Number} pageThepageparam that was passed in[S]{Number} numPagesThe number of pages based ontotalandlimit[S]{Boolean} isEmptyTrue if no records were returned[S]{String} locationThe value of theLocationresponse header[S]{String} contentTypeThe value of theContent-Typeresponse header[S]{String} contentLengthThe value of theContent-Lengthresponse header[S]{Array} noticesThe parsed value of theAPI-Response-Noticesresponse header[S]{Array} errorsThe parsed value of theAPI-Response-Errorsresponse header[S]{String} responseIdThe value of theAPI-Response-Idresponse header[S]{Number} newIdThe value of theAPI-New-Record-Idresponse header[S]{Number} timeThe value of theAPI-Response-Timeresponse header[S]{Boolean} wasAbortedTrue if request was aborted[S]
Handling Errors
ApiError is a custom Sharpr class to make it easier to work with response errors.
api.get('/hello').then(onSuccess, response => {
// response is instance of ApiError
// response.error is instance of Error
});ApiError has the same properties of ApiResponse plus
{Error} errorThe HTTPError or TimeoutError object[S]
Special methods
The following methods are added for convenience:
patchDifference(endpoint, oldValues, newValues, options)submitJob(endpoint, payload, options)
Examples
patchDifference example:
const currState = {
fname: 'John',
lname: 'Doe',
};
const newState = {
fname: 'Johnny',
lname: 'Doe',
};
api
.patchDifference('/v2/users/123', currState, newState)
.then(onSuccess, onError);submitJob example:
const jobInfo = {
post_ids: [1, 2, 3, 4],
action: 'destroy',
};
const onSuccess = response => {
const recheckIntervalMs = 5000;
response.onJobComplete(() => {
alert('Posts were all deleted!');
}, recheckIntervalMs);
};
api.submitJob('/v2/posts/massdelete', jobInfo).then(onSuccess, onError);Aborting requests
const { abort, get } = require('api-client');
// abort all pending requests
abort();
// abort all pending GET requests
abort('get');
// abort all pending GET /posts/123 requests
abort('get', '/posts/123');
// abort a specific request
const promise = get('/posts/123');
abort(promise);Hooks
There are some api-related hooks that can be used with React components:
useApiGet(endpoint, params, options)useApiGetAll(endpointArgSets)useApiEndpoint(verb, endpoint)
useApiGet
Example:
export function MyComponent() {
const { isLoading, hasError, response } = useApiGet(
'https://httpbin.org/get?a=1'
);
return (
<div>
{isLoading && <Loader size={16} />}
{hasError && <div>Error fetching data</div>}
{response && <div>a equals {response.data.args.a}</div>}
</div>
);
}Consolidating multiple useApiGet calls using withResponses
Example:
import useApiGet from '../../libs/api/hooks/useApiGet/useApiGet.js';
import withResponses from '../../libs/api/middleware/withResponses/withResponses.js';
export function MyComponent() {
const { isLoading, hasError, responses } = withResponses([
useApiGet('https://httpbin.org/get', { a: 1 }),
useApiGet('https://httpbin.org/get', { b: 2 }),
]);
return (
<div>
{isLoading && <Loader size={16} />}
{hasError && <div>Error fetching data</div>}
{responses &&
responses.map((response, i) => (
<div key={i}>{JSON.stringify(response.data.args)}</div>
))}
</div>
);
}useApiEndpoint
Example:
export function MyComponent() {
const verb = 'get';
const endpoint = '/v3/posts/search';
const { isLoading, hasError, response, request, abort } = useApiEndpoint(
verb,
endpoint
);
const [term, setTerm] = useState('');
const doSearch = () => {
abort(); // abort any incomplete request
request({ term }); // make a request
};
return (
<div>
<form onSubmit={doSearch}>
Search Posts:
<input value={term} onChange={e => setTerm(e.target.value)} />
<button>Go</button>
</form>
{isLoading && <Loader size={16} />}
{hasError && <div>Error fetching data</div>}
{response &&
response.data.map(post => (
<div key={post.id}>
{post.headline} - {post.summary}
</div>
))}
</div>
);
}Caching
To cache a response for later, use the cacheFor option.
cacheFor may be a number of milliseconds or a time expression such as the following:
1d=> 1 day8h=> 8 hours20m=> 20 minutes30s=> 30 seconds300ms=> 300 milliseconds3h 20m=> 3 hours, 20 minutes
See parse-duration on npm for more supported expressions.
Example:
const result1 = api.get('/abc', { d: 4 }, { cacheFor: '2h' });
// ... 1 hour later ...
const result2 = api.get('/abc', { d: 4 }, { cacheFor: '2h' });
// result1 === result2Mocking with fetch-mock
For unit tests, you may want to mock responses. The fetch-mock package works well for that.
Example:
// first require or import api-client
const api = require('api-client');
// fetchMock must be required aftwarwords
const fetchMock = require('fetch-mock');
fetchMock.get(/posts\/search/, {
status: 200,
body: range(10).map(id => ({ id, title: `Post ${id}` })),
headers: {
'API-Total-Records': '200',
},
});
api.get('/api/v2/posts/search?limit=10').then(response => {
response.data; // equal to body specified above
response.total; // 200
response.numPages; // 20
});Interceptors
requestCalled before every request is sentresponseCalled after every response is receoved (before resolving promise)errorCalled when HTTP status is between 400 and 599 inclusive (before rejecting promise)timeoutCalled when an API request hits the configured timeout (before rejecting promise)abortCalled when the promise creator aborts the request (before rejecting promise)
// NOTE:
// request instanceof ApiRequest
// response instanceof ApiResponse
// api instanceof ApiService
// error instanceof ApiError
api.addInterceptor({
request: (request, api) => {},
response: (request, response, api) => {},
error: (request, error, api) => {},
timeout: (request, error, api) => {},
abort: (request, error, api) => {},
});ApiRequest
| Property | Type | Description | Examples |
|---|---|---|---|
method | String | The HTTP verb uppercase | GET, POST |
endpoint | String | The Sharpr endpoint | /v3/users/me |
params | Object | The params to send in the URL | {a: '1'} |
data | Object | The payload to be sent | { a: 1 } |
options | Object | The options passed to fetch | { cacheFor: '15m' } |
headers | Headers | Headers to be sent | new Headers({ a: '1'}) |
queryString | String | The URL query string based on params | a=1&b=2 |
url | String | The full URL to be sent | https://example.com/abc?a=1&b=2 |
pending | Boolean | True when request started but not completed | false |
completed | Boolean | True when request has completed | true |
| Method | Returns | Description |
|---|---|---|
abort() | void | Abort a pending request |
send() | Promise | A promise from fetch |