The module has two exports:
const { fetch } = require('gofer');
method
: The HTTP verb, e.g. 'GET' or 'POST'.headers
: A plain object with header names and values. E.g.{'content-type': 'text/x-cats'}
.auth
: Either a string of the formusername:password
or an object withusername
andpassword
properties that will be used to generate a basic authorizationheader.baseUrl
: Prefix for theurl
parameter.fetch('/echo', { baseUrl: 'https://my-api.com/v2' })
will load https://my-api.com/v2/echo.pathParams
: Values for placeholders in the url path. E.g.{ foo: 'bar' }
will replace all occurrences of{foo}
in the url path with'bar'
.qs
: Additional query parameters that will be combined with those present in the initial path/url arg. Ifqs
is a plain JS object, the contents will be recursively serialized using an algorithm similar to that of theqs
library. If it is a JavaScriptURLSearchParams
object, each entry in it will be.append()
ed to any existing query parameters.body
: Content of the request body, either as a string, a Buffer, or a readable stream.json
: Content of the request body to be sent as a JSON encoded value.form
: Content of the request body to be sent usingx-www-form-urlencoded
. The serialization is the same as that used by theqs
option.- All TLS socket options for https requests.
maxSockets
: Maximum number of parallel requests to the same domain.gofer
will never use the global http(s) agent but will instead keep agents per client class.timeout
: Response- and socket read timeout in milliseconds (default: 10000)- if the socket is idle for more than this time, there will be an ESOCKETTIMEDOUT (NodeJS only)
- if it takes longer to receive response headers (measured from when the request function is first called), there will be an ETIMEDOUT
connectTimeout
: Timeout in milliseconds for the time between acquiring a socket and establishing a connection to the remote host. This should generally be relatively low. (default: 1000) (NodeJS only)completionTimeout
: Timeout in milliseconds between when the response headers have been received, and when the final byte of the response body has been received. (no default; disabled by default) (NodeJS only)searchDomain
: Inspired by thesearch
setting in/etc/resolv.conf
. Append this to any hostname that doesn't already end in a ".". E.g.my-hostname
turns intomy-hostname.<searchDomain>.
butmy.fully.qualified.name.
won't be touched.keepAlive
: if set totrue
, enables HTTP keep-alive ⚠ EnablingkeepAlive
can lead toMaxListenersExceededWarning: Possible EventEmitter memory leak detected.
warnings.captureAsyncStack
: Extends error trace with stack trace before call. (Default:false
) Capturing the stack is expensive! Set only for debugging purposessignal
- AbortController signal (Node 15+)
fetch()
returns a Promise for, or calls the callback specified with a
response object, which supports the following methods. For convenience
in Promise mode, you may also invoke each of them directly on the Promise
returned from fetch()
, i.e. you may do:
fetch(url).then(res => res.json()).then(obj => console.log(obj.status));
// OR
fetch(url).json().then(obj => console.log(obj.status));
Returns a Promise for the entire response text, parsed as JSON
Returns a Promise for the entire response, charset decoded
Returns a Promise for the raw response body, as a Buffer
Returns a stream for the data as it arrives
This class can be used directly, but it's mainly meant to be the base class for individual service clients. Example:
const Gofer = require('gofer');
const { version, name } = require('./package.json');
class MyClient extends Gofer {
constructor(config) {
super(config, 'myService', version, name);
}
/* endpoint definitions here */
}
All parts of the configuration are just default options. There are three levels of configuration:
config.globalDefaults
: Applies for calls to all services.config[serviceName]
: Only applies to calls to one specific service.config[serviceName].endpointDefaults[endpointName]
: Only applies to calls using a specific endpoint.
More specific configuration wins, e.g. an endpoint-level default takes precendence over a service-level default.
Example:
const Gofer = require('gofer');
const config = {
globalDefaults: { timeout: 100, connectTimeout: 55 },
a: { timeout: 1001 },
b: {
timeout: 99,
connectTimeout: 70,
endpointDefaults: { x: { timeout: 23 } },
},
};
class GoferA extends Gofer {
constructor(config) { super(config, 'a'); }
}
class GoferB extends Gofer {
constructor(config) { super(config, 'b'); }
x() {
return this.get('/something', { endpointName: 'x' });
}
}
const a = new GoferA(config), b = new GoferB(config);
a.fetch('/something'); // will use timeout: 1001, connectTimeout: 55
b.fetch('/something'); // will use timeout: 99, connectTimeout: 70
b.x(); // will use timeout: 23, connectTimeout: 70
All service-specific behavior is implemented using option mappers.
Whenever an request is made, either via an endpoint or directly via
gofer.fetch
, the options go through the following steps:
- The endpoint defaults are applied if the request was made through an endpoint.
options.serviceName
andoptions.serviceVersion
is added.options.methodName
andoptions.endpointName
is added. The former defaults to the http verb but can be set to a custom value (e.g.addFriend
). The latter is only set if the request was made through an endpoint method.- The service-specific and global defaults are applied.
- For every registered option mapper
m
theoptions
are set tom(options) || options
. - A
User-Agent
header is added if not present already. null
andundefined
values are removed fromqs
andheaders
. If you want to pass empty values, you should use an empty string.
Step 6 implies that every option mapper is a function that takes one argument
options
and returns transformed options or a falsy value. Inside of the
mapper this
refers to the gofer
instance. The example contains an option
mapper that handles access tokens and a default base url.
Add a new option mapper to all instances using the prototype. This can also be called on an instance which doesn't have a global effect.
mapFn
: An option mapper, see option mappers
Registers "endpoints".
Endpoints are convenience methods for easier construction of API calls
and can also improve logging/tracing.
The following conditions are to be met by endpointMap
:
- It maps a string identifier that is a valid property name to a function.
- The function takes one argument which is
fetch
. fetch
works likegofer.fetch
only that it's aware of endpoint defaults.
Whatever the function returns will be available as a property on instances of the class. Common variants are a function or a nested objects with functions.
MyService.prototype.registerEndpoints({
simple(fetch) {
return cb => fetch('/some-path', cb);
},
complex(fetch) {
return {
foo(qs, cb) {
return fetch('/foo', { qs: qs }, cb);
},
bar(entity, cb) {
return fetch('/bar', { json: entity, method: 'PUT' }, cb);
}
}
}
});
const my = new MyService();
my.simple(); // returns a Promise
my.complex.foo({ limit: 1 }); // returns a Promise
my.complex.bar({ name: 'Jordan', friends: 231 }, (err, body) => {});
Creates a new instance with the exact same settings and referring to the same
hub
.
Returns a copy with overrideConfig
merged
into both the endpoint- and the service-level defaults.
Useful if you know that you'll need custom timeouts for this one call
or you want to add an accessToken.
url
: The url to fetch. May be relative tooptions.baseUrl
.options
: Anything listed below under optionscb
: A callback function that receives the following arguments:error
: An instance ofError
orundefined
/null
.body
: The (generally parsed) response body.response
: The response object with headers and statusCode.
Unless a cb
is provided, the function will return a Promise
.
If a cb
is provided, it will return undefined.
If an HTTP status code outside of the accepted range is returned,
the error will be a StatusCodeError
with the following properties:
method
: The request method.url
: The full URL that was requested.headers
: The headers of the response.body
: The, in most cases parsed, response body.statusCode
: The actual HTTP status code.minStatusCode
: The lower bound of accepted status codes.maxStatusCode
: The upper bound of accepted status codes.
The accepted range of status codes is part of the configuration. It defaults to accepting 2xx codes only.
If there's an error that prevents any response from being returned,
you can look for code
to find out what happened. Possible values include:
ECONNECTTIMEDOUT
: It took longer thanoptions.connectTimeout
allowed to establish a connection.ETIMEDOUT
: Request took longer thanoptions.timeout
allowed.ESOCKETTIMEDOUT
: Same asETIMEDOUT
but signifies that headers were received.EPIPE
: Writing to the request failed.ECONNREFUSED
: The remote host refused the connection, e.g. because nothing was listening on the port.ENOTFOUND
: The hostname failed to resolve.ECONNRESET
: The remote host dropped the connection. E.g. you are talking to another node based service and a process died.