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-fetch
it 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} method
GET, POST, DELETE, etc.[f]
-
{String} endpoint
The 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} headers
Any request headers[f]
-
{Number} timeout
The number of milliseconds after which to time out (default is 5 minutes)[S]
-
{Boolean} avoidLoadingBar
If 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/123
Is changed tohttps://sharpr.com/api/v2/posts/123
-
/v3/posts/search
Is changed tohttps://sharpr.com/api/v3/posts/search
-
https://sharprua.com/status/tests.php
Is 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} request
The original ApiRequest object that generated this response-
{String} url
A raw URL to use instead of the endpoint[S]
-
{String} method
GET, POST, DELETE, etc.[f]
-
{Object} searchParams
params that were serialized into the GET string[k]
-
{Object} headers
Any request headers[f]
-
{*} json
The JSON payload[f]
-
{Number} timeout
The number of milliseconds after which to time out[S]
-
{Boolean} avoidLoadingBar
If true, do not show loading bar[S]
-
-
{String} type
Either "json", "text" or null depending on response type[S]
-
{*} data
The decoded response or text[S]
-
{String} endpoint
The name of the API endpoint such as /posts/123[S]
-
{Response} rawResponse
The rawfetch
response[S]
-
{Boolean} ok
True if response is 2xx[f]
-
{Number} status
The status code[f]
-
{String} statusText
Status name such as "OK" or "No Content"[f]
-
{Headers} headers
Headers object withget()
,has()
, etc. -
{Number} total
The value ofAPI-Total-Records
response header[S]
-
{Number} size
The number of items returned in Request.data[S]
-
{Number} limit
Thelimit
param that was passed in[S]
-
{Number} page
Thepage
param that was passed in[S]
-
{Number} numPages
The number of pages based ontotal
andlimit
[S]
-
{Boolean} isEmpty
True if no records were returned[S]
-
{String} location
The value of theLocation
response header[S]
-
{String} contentType
The value of theContent-Type
response header[S]
-
{String} contentLength
The value of theContent-Length
response header[S]
-
{Array} notices
The parsed value of theAPI-Response-Notices
response header[S]
-
{Array} errors
The parsed value of theAPI-Response-Errors
response header[S]
-
{String} responseId
The value of theAPI-Response-Id
response header[S]
-
{Number} newId
The value of theAPI-New-Record-Id
response header[S]
-
{Number} time
The value of theAPI-Response-Time
response header[S]
-
{Boolean} wasAborted
True 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} error
The 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 day -
8h
=> 8 hours -
20m
=> 20 minutes -
30s
=> 30 seconds -
300ms
=> 300 milliseconds -
3h 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 === result2
Mocking 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
-
request
Called before every request is sent -
response
Called after every response is receoved (before resolving promise) -
error
Called when HTTP status is between 400 and 599 inclusive (before rejecting promise) -
timeout
Called when an API request hits the configured timeout (before rejecting promise) -
abort
Called 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 |