@procore/core-http
@procore/core-http
A simple HTTP client. Built on top of up-fetch!
yarn add @procore/core-httpThis simple wrapper over native fetch makes interacting with the Procore API a little easier. By default, it injects a CSRF token from known places when working from inside of an Micro Front End, or in a Hydra Client, but also provides customization and an extension mechanism.
Usage
Simple Example
import { createClient } from '@procore/core-http'
const client = createClient({
errorReportingApiKey: process.env.BUGSNAG_API_KEY ?? '',
})
function ExampleRequest() {
client('/some-url-here')
.then((data) => {
// Contains the actual result. No need to call response.json() or check response.ok
console.log(data)
})
.catch((err) => {
console.error(err)
})
}Customize options, such as baseUrl
import { createClient } from '@procore/core-http'
const client = createClient({
errorReportingApiKey: process.env.BUGSNAG_API_KEY ?? '',
defaults: () => ({
baseUrl: 'https://www.example.com', // uses this baseUrl for every request.
}),
})
function ExampleRequest() {
client('/some-url-here') // request will be 'https://www.example.com/some-url-here'
.then((data) => {
// Contains the actual result. No need to call response.json() or check response.ok
console.log(data)
})
.catch((err) => {
console.error(err)
})
}
// Or, one can override an option on a request-by-request basis:
const client = createClient({
errorReportingApiKey: process.env.BUGSNAG_API_KEY ?? '',
systemEvents, // pass your app's instance so the host receives events with a more accurate source
})
client('/some-url-here', { baseUrl: 'https://www.example.com' })API
createClient({ errorReportingApiKey, defaults?, plugins?, fetchFn?, defaultErrorHandling?, systemEvents? })
createClient({
errorReportingApiKey: string,
defaults: () => RequestOptions,
plugins: Array<Plugin>,
fetchFn: typeof fetch,
defaultErrorHandling: boolean,
systemEvents: SystemEvents,
}): clientsystemEvents - optional (default: new SystemEvents('core-http'))
The event bus used to publish UNCAUGHT_EXCEPTION when requests fail. Pass your app's SystemEvents instance so the host receives events with a more accurate source as part of its payload when UNCAUGHT_EXCEPTION is dispatched. If omitted, a new instance of SystemEvents is created with source core-http.
defaults - optional (default: () => ({}))
A function that returns the default request parameters that should be used on every request. It maybe be a synchronous or asynchronous function. Equivalent to the getDefaultOptions function passed to the up function in up-fetch. Refer to the getDefaultOptions API docs for all of the options.
plugins - optional (default: [])
A collection of plugins to augment the behavior of each request. Refer to the "Plugins" below for more details.
fetchFn - optional (default: fetch)
The function that is used to actually make the fetch calls. Equivalent to the fetch parameter in the up function in up-fetch. The default should work for most cases. The only reason to consider overriding it is perhaps for unit tests, although there are better options.
defaultErrorHandling - optional (default: true)
When true, createClient will publish a SystemEventNames.UNCAUGHT_EXCEPTION event when a request fails. To opt out per request, pass defaultErrorHandling: false in request options. When false, createClient will always throw errors and will not publish the system event.
errorReportingApiKey - required
This value is included in the UNCAUGHT_EXCEPTION event payload as errorReportingApiKey. When it is an empty string, default error reporting is effectively disabled (exceptions are still thrown).
isResponseError(error: unknown): boolean
Returns true when the error is a response error. Exported from up-fetch.
import { isResponseError } from '@procore/core-http'
async function makeRequest() {
try {
// makes a core-http fetch call
} catch (error: unknown) {
if (isResponseError(error)) {
// handle the strongly-typed response error
} else {
throw error // re-throw for the new handler, or handle it differently
}
}
}client(url, options?)
The function returned from createClient. Used to make fetch requests.
function upfetch(
url: string | URL | Request,
options?: FetcherOptions
): Promise<any>url - required
The request object. This is be a string, URL, or Request object.
options - optional (default: {})
Additional options to add to this specific request. Note that any properties provided here will override the defaults.
Additional information about the available options can be found in the up-fetch docs.
isValidationError(error: unknown): boolean
Returns true when the error is a schema validation error. Exported from up-fetch.
import { isValidationError } from '@procore/core-http'
async function makeRequest() {
try {
// makes a core-http fetch call
} catch (error: unknown) {
if (isValidationError(error)) {
// handle the strongly-typed validation error
} else {
throw error // re-throw for the new handler, or handle it differently
}
}
}request (deprecated)
Makes a fetch request, with no additional handling. It is strongly recommended to use createClient instead of this method.
request(url: string, requestParams: RequestParams)url - required
The resource to fetch
requestParams - optional (default: {})
Used to augment the resulting fetch call with additional configuration, such as method, baseUrl, and errorReportingApiKey.
errorReportingApiKey- optional. When non-empty, enables error reporting for failed requests. Defaults to''(disabled; no reporting).systemEvents- optional. Specifies whichSystemEventsinstance/event bus to use so the host receives events with a more accurate source as part of its payload whenUNCAUGHT_EXCEPTIONis dispatched. If omitted, a new instance ofSystemEventsis created with sourcecore-http.
import { request } from '@procore/core-http'
function ExampleRequest() {
request('/some-url-here')
.then((response) => console.log(response))
.catch((err) => console.error(err))
}requestJSON (deprecated)
Fetches a resource, and attempts to cast the parsed JSON response as the specified type. It is strongly recommended to use createClient instead of this method, which supports both schema validation and error handling.
requestJSON<T>(url: string, requestParams: RequestParams)url - required
The resource to fetch
requestParams - optional (default: {})
Used to augment the resulting fetch call with additional configuration, such as method and baseUrl.
import { requestJSON } from '@procore/core-http'
function ExampleRequest() {
requestJSON('/some-url-here.json')
.then((response) => console.log(response))
.catch((err) => console.error(err))
}Plugins
Plugins provide an opportunity augment the behavior that occurs with each request. Examples of plugin behavior include:
- Adding a header to every request.
- Emit an event based on an 4xx or 5xx error response.
- Display a modal based on an error response.
- Instrument requests and responses with Open Telemetry or logging.
The plugin interface is as follows:
interface Plugin {
onError?: (error, request) => void
onRequest?: (request) => void
onSuccess?: (data, request) => void
}onRequest functions are run when a request is initiated. onSuccess is called when a request successfully returns. Finally, onError is called when an error response is received. Refer to the "Lifecycle Hooks" section in the up-fetch docs for more details about each method.
There are a few caveats to understand when working with lifecycle hooks:
- Order matters! Lifecycle hooks execute in the following order:
- Lifecycle hooks added to default options
- Lifecycle hooks in each plugin, in order
- Lifecycle hooks added to an individual request
- Plugins only need to implement the lifecycle hooks that they need. For example, when implementing a plugin that only handle error responses, you can omit the
onRequestandonSuccessmethods. - The
defaultErrorHandlingoption controls how errors are handled. By default, errors are still thrown even if you provide anonErrorhandler; useonErrorfor logging or other side effects rather than to suppress the error.
Additional Considerations
createClient opens up all of the functionality of up-fetch, while providing some Procore-specific defaults. Features that you should consider using include:
- Simple query parameters: the library supports specifying query parameters via a simple
{params: {}}property passed to your request. - Automatic body handling: the library supports automatic serialization of the body on
POSTcalls. - Schema validation: the library supports Standard Schema. This will not only validate the responses that you receive from a fetch call, but also return the data in the validate type (no more type casting!).