Simple Node.js and Browser REST client
backendless.js => ~ 28 KB
backendless.min.js => ~ 11 KB
for installation just execute the following command:
npm i backendless-request -S
import BackendlessRequest from 'backendless-request';
//or
const BackendlessRequest = require('backendless-request');Inside the installed package you can find a dist directory, where are two js files backendless-request.js and backendless-request.min.js
-|
- dist
|-backendless.js
|-backendless.min.js
Get one of the js files into your project
<script src="/path-to-backendless-request-package/dist/backendless-request.js"></script>you can use minimized file as well
<script src="/path-to-backendless-request-package/dist/backendless-request.min.js"></script>After that you can use BackendlessRequest from the global scope
BackendlessRequest.get('http://foo.bar/')Since the JS-SDK already uses the module for API requests, therefor if you use the SDK in your code you can use the Request module as well in your code without additional require, see the example below:
import Backendless from 'backendless';
Backendless.Request.get('https://foo.bar/')
.then(result => console.log(result))
.catch(error => console.error(error))The Backendless UI Builder and JS Cloud Code include the Backendless JS-SDK in the global scope, therefor the Request module is also available there, see the example below:
Backendless.Request.get('https://foo.bar/')
.then(result => console.log(result))
.catch(error => console.error(error))BackendlessRequest.get('https://foo.bar/')
.then(result => console.log(result))
.catch(error => console.error(error))BackendlessRequest.post('https://foo.bar/', { foo: 'bar', bool: true, num: 1 })
.then(result => console.log(result))
.catch(error => console.error(error))BackendlessRequest.put('https://foo.bar/', { num: 123 })
.then(result => console.log(result))
.catch(error => console.error(error))BackendlessRequest.delete('https://foo.bar/foo')
.then(result => console.log(result))
.catch(error => console.error(error))BackendlessRequest.patch('https://foo.bar/foo', { bool: false })
.then(result => console.log(result))
.catch(error => console.error(error))A HEAD response has no body, so the useful part of the result are the response headers,
use .unwrapBody(false) to get the whole response instead of its body
BackendlessRequest.head('https://foo.bar/foo')
.unwrapBody(false)
.then(result => console.log(result.status, result.headers))
.catch(error => console.error(error))BackendlessRequest.options('https://foo.bar/foo')
.unwrapBody(false)
.then(result => console.log(result.headers))
.catch(error => console.error(error))You can set up a request query through .query(query) method
and the library will automatically add the query to request url
// RequestUrl: https://foo.bar/some-path?str=some-string&num=123&bool=true&arr=1&arr=2&arr=3&arr=4
BackendlessRequest.get('https://foo.bar/some-path')
.query({ str: 'some-string', num: 123, bool: true, list: [1, 2, 3, 4] })
.then(result => console.log(result))
.catch(error => console.error(error))You can send request body through the .send(body) method,
but if you don't do that, the method will be called with the second argument when you call then or catch method.
BackendlessRequest.post('https://foo.bar/some-path')
.send({ str: 'some-string', num: 123, bool: true, list: [1, 2, 3, 4] })
.then(result => console.log(result))
.catch(error => console.error(error))BackendlessRequest.post('https://foo.bar/some-path', { str: 'some-string', num: 123, bool: true, list: [1, 2, 3, 4] })
.then(result => console.log(result))
.catch(error => console.error(error))For sending a form you should use .form(form) method
BackendlessRequest.post('http://foo.bar/')
.form(form)
.then(result => console.log(result))
.catch(error => console.error(error))The method accepts a plain object, an instance of Request.FormData
and a WHATWG FormData - the global one of Node 18+ and the browser one:
const form = new FormData() // the global FormData
form.append('title', 'a report')
form.append('file', new Blob([bytes]), 'report.pdf')
await BackendlessRequest.post('http://foo.bar/').form(form)A Blob and a File are supported as a form value as well, in Node js they are streamed and
their size is used for the content-length header, so a large file is not kept in memory twice:
const bytes = await BackendlessRequest.get('http://foo.bar/report.pdf').toBuffer()
await BackendlessRequest.post('http://vendor.bar/upload')
.form({
// a Buffer, a stream, a Blob, a File and a primitive value are all accepted
file : new Blob([bytes], { type: 'application/pdf' }),
// the { value, options } form still works and its options win
other: { value: bytes, options: { filename: 'report.pdf', contentType: 'application/pdf' } },
})A nameless Blob is sent as filename="blob", the same as a browser does.
A value which can not be sent as a form part is reported with an error naming the field,
instead of an Illegal invocation/source.on is not a function thrown from inside the
form streaming later on.
In Node js
Request.FormDatais the form-data package, and it accepts only a Buffer, a stream or a primitive value. Appending aBlobdirectly to such an instance still throwssource.on is not a functionat append time - that happens inside the package, before this library sees the form. Pass the Blob through.form(), or use the globalFormData, and it will be converted for you.
To manually set up the Content-Type header, you can use the .type(contentTypeHeader) method or set it via .set('Content-Type', value) method.
If you pass an object as a request body the Content-Type header will be automatically specified as application/json
BackendlessRequest.get('https://foo.bar/')
.set('x-header-key', 'x-header-value')
.set({ 'y-header-key': 'y-header-value', 'z-header-key': 'z-header-value' })
.then(result => console.log(result))
.catch(error => console.error(error))Use .toBuffer() to download raw bytes - a PDF, an image, a video - and forward them somewhere else.
The method is terminal, it sends the request and always resolves the body as bytes:
a Buffer in Node js and a Uint8Array in a browser.
Since a Buffer is a Uint8Array, result instanceof Uint8Array is a check which holds in both environments.
const bytes = await BackendlessRequest.get('https://foo.bar/report.pdf').toBuffer()
form.append('file', bytes, 'report.pdf')The guarantee is unconditional, unlike .setEncoding(null):
- the body is never passed to
JSON.parse, so a downloaded file is returned byte for byte even when its content happens to be valid JSON ({"a":1},42,null,"abc"); - a response with no body, a
204or aContent-Length: 0, resolves to an empty buffer, never to''; - there is no need to call
.setEncoding(null)as well,.toBuffer()implies a raw transfer and overrides an encoding which was set before it.
An unsuccessful response is rejected as usual, and its body is still parsed when it is JSON, so an error message coming from the server stays readable:
try {
const bytes = await BackendlessRequest.get('https://foo.bar/report.pdf').toBuffer()
} catch (error) {
console.error(error.status) // 404
console.error(error.body.message) // the parsed error body of the server
}.unwrapBody(false) works together with .toBuffer(), in that case the whole response is resolved
and its body is the bytes.
.setEncoding(null)keeps its current behaviour for backward compatibility, including passing the body toJSON.parse. Prefer.toBuffer()for any binary download.
Some APIs, most of them in regulated banking, require the client to present a TLS certificate of its own
before they will answer at all. Use the .cert(tlsOptions) method to attach one.
BackendlessRequest.post('https://api.vendor.com/some-path')
.cert({
cert : clientCertPem, // PEM encoded client certificate (or chain)
key : clientKeyPem, // PEM encoded private key
passphrase: keyPassphrase, // optional, for an encrypted private key
ca : caBundlePem // optional, a custom CA bundle to verify the server with
})
.set({ 'Content-Type': 'application/json' })
.send(body)
.then(result => console.log(result))
.catch(error => console.error(error))Every option accepts a String, a Buffer or an array of them, exactly as the Node
TLS options do.
Options other than the four above are ignored, so a connection can not be weakened by accident.
The method can be called several times and the options are merged, which lets the certificate and the key come from different places:
BackendlessRequest.get('https://api.vendor.com/accounts')
.cert({ cert: clientCertPem, key: clientKeyPem })
.cert({ ca: caBundlePem })A few things worth knowing:
- The certificate must be attached to the token request too. Servers of this kind usually demand the certificate before authentication, so their OAuth2 token endpoint is unreachable without it as well.
- It works in Node.js only. In a browser
XMLHttpRequestgives no control over the client certificate, the browser picks one from the keystore itself, so.cert()there does nothing. - A client certificate requires an
https://URL. Sending it overhttp://would silently drop it and put the request on the wire in plain text, so the request fails instead. careplaces the default trust store for that request, the same waycurl --cacertdoes. Pass the full chain that is needed to verify the server.- The private key is credential material. Store it the way you store a password, never inline it into a
URL. The library keeps it out of
BackendlessRequest.verboseoutput and off the enumerable properties of the request, so it does not end up in a log throughconsole.log(request)orJSON.stringify(request).
A failed handshake would otherwise surface as an opaque socket error, so recognized failures are raised as a
TLSError with a message that says what went wrong. error.code keeps the original Node/OpenSSL code and
error.cause the original error.
The server requires a client certificate and none was supplied. (ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED)
The server rejected the client certificate because it has expired. (EPROTO)
The private key could not be decrypted, the passphrase is missing or incorrect. (ERR_OSSL_BAD_DECRYPT)
The client certificate or the private key is not valid PEM. (ERR_OSSL_PEM_NO_START_LINE)
The server certificate has expired. (CERT_HAS_EXPIRED)
The server certificate could not be verified, a CA bundle may be required. (UNABLE_TO_VERIFY_LEAF_SIGNATURE)
When a server just drops the connection instead of sending a TLS alert, which is what a TLS 1.3 server typically does when it does not accept the certificate, the error says so:
The connection was closed during the TLS handshake, the client certificate was most likely missing,
rejected or expired. (ECONNRESET)
Errors which have nothing to do with TLS are left untouched.
A request instance might fire events to notify about changing request state:
for subscribing use method .on(<eventName>, callback)
BackendlessRequest.post('https://foo.bar/some-path')
.on('request', req => req.set('my-x-header-key', 'my-x-header-value'))
.on('response', result => console.log('result', result))
.on('error', error => console.log('error', error))
.on('done', (error, result) => console.log('done', { error, result }))
.send({ str: 'some-string', num: 123, bool: true, list: [1, 2, 3, 4] })
.then(result => console.log(result))
.catch(error => console.error(error))request- it will be fired before sending a request to the serverresponse- it will be fired when a request is successfully completederror- it will be fired when a request is faileddone- it will be fired when a request is done, it's a shortcut forresponseanderror
You can use your own XMLHttpRequest, just replace it in BackendlessRequest namespace.
class MySupperXMLHttpRequest {
open(){
}
...
send(){
}
}
BackendlessRequest.XMLHttpRequest = MySupperXMLHttpRequestIf you want to log all the requests just set true for verbose
BackendlessRequest.verbose = true