Skip to content

Repository files navigation

Backendless Request

npm version

Simple Node.js and Browser REST client

backendless.js => ~ 28 KB
backendless.min.js => ~ 11 KB

How to use

Install

for installation just execute the following command:

npm i backendless-request -S

Require it as a module

import BackendlessRequest from 'backendless-request';

//or

const BackendlessRequest = require('backendless-request');

Include it as a single file

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/')

As a part of the JS-SDK

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))

In the UI Builder and JS Cloud Code environment

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))

Request Methods

GET

BackendlessRequest.get('https://foo.bar/')
  .then(result => console.log(result))
  .catch(error => console.error(error))

POST

BackendlessRequest.post('https://foo.bar/', { foo: 'bar', bool: true, num: 1 })
  .then(result => console.log(result))
  .catch(error => console.error(error))

PUT

BackendlessRequest.put('https://foo.bar/', { num: 123 })
  .then(result => console.log(result))
  .catch(error => console.error(error))

DELETE

BackendlessRequest.delete('https://foo.bar/foo')
  .then(result => console.log(result))
  .catch(error => console.error(error))

PATCH

BackendlessRequest.patch('https://foo.bar/foo', { bool: false })
  .then(result => console.log(result))
  .catch(error => console.error(error))

HEAD

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))

OPTIONS

BackendlessRequest.options('https://foo.bar/foo')
  .unwrapBody(false)
  .then(result => console.log(result.headers))
  .catch(error => console.error(error))

Query Params

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))

Request Body

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))

Form

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.FormData is the form-data package, and it accepts only a Buffer, a stream or a primitive value. Appending a Blob directly to such an instance still throws source.on is not a function at append time - that happens inside the package, before this library sees the form. Pass the Blob through .form(), or use the global FormData, and it will be converted for you.

ContentType Header

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))

Downloading Binary Data

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 204 or a Content-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 to JSON.parse. Prefer .toBuffer() for any binary download.

Client Certificate (mutual TLS)

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 XMLHttpRequest gives 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 over http:// would silently drop it and put the request on the wire in plain text, so the request fails instead.
  • ca replaces the default trust store for that request, the same way curl --cacert does. 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.verbose output and off the enumerable properties of the request, so it does not end up in a log through console.log(request) or JSON.stringify(request).

TLS errors

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.

Request Events

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 server
  • response - it will be fired when a request is successfully completed
  • error - it will be fired when a request is failed
  • done - it will be fired when a request is done, it's a shortcut for response and error

Own XMLHttpRequest

You can use your own XMLHttpRequest, just replace it in BackendlessRequest namespace.

See Example

class MySupperXMLHttpRequest {
  
  open(){
    
  }
  
  ...
  
  send(){
    
  }
}

BackendlessRequest.XMLHttpRequest = MySupperXMLHttpRequest

Logging

If you want to log all the requests just set true for verbose

BackendlessRequest.verbose = true

About

Simple Node.js and browser REST client

Resources

Stars

4 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages