Functions

In order to consume an API with Bearer, you need to write functions.

These functions let you query & transform APIs into a structure & logic that your App natively understands and can consume using a single integration client.

Functions act as a middleware layer between your app and APIs, like a custom BFF. Therefore, your app will not be affected by the glue code of the integration.

Generating Functions

To help you create functions we recommend to using the generator from the CLI:

NPM
Yarn
npm run bearer generate:function myfunction
yarn bearer generate:function myfunction

It will create a function boilerplate with everything necessary to start querying the API and managing your transformation:

functions/myfunction.ts
import { FetchData, TFetchActionEvent, TFetchPromise } from '@bearer/functions'
// Uncomment the line below to use the API Client
// import Client from './client'
export default class MyfunctionFunction extends FetchData implements FetchData<ReturnedData, any> {
async action(event: TFetchActionEvent<Params>): TFetchPromise<ReturnedData> {
// Put your logic here
return { data: [] }
}
// Uncomment the line below to restrict the function to be called only from a server-side context
// static serverSideRestricted = true
}
/**
* Typing
*/
export type Params = {
// name: string
}
export type ReturnedData = {
// foo: string[]
}

The generated code will vary depending on the API authentication mechanism of the integration.

Managing Types

Functions are written in TypeScript (a typed version of JavaScript), making your code more robust and secure.

Bearer also takes advantage of TypeScript by using Types to automatically create function's Specification aka the Open API documentation.

By default, generated functions provide an empty Types definition:

/**
* Typing
*/
export type Params = {
// name: string
}
export type ReturnedData = {
// foo: string
}

The Params hash corresponds to the function arguments (if any). The ReturnedData hash corresponds to the function return.

For example, a function returning name, description, and url will have a Type defined as:

export type ReturnedData = {
name: string
description: string
url: string
}

Here is a list of all the basic Types available in TypeScript.

Using the API Client

To help query APIs, a Client is provided in the integration by default:

functions/client.ts
import axios from 'axios'
export default function(token: string) {
const headers = {
'Accept': 'application/json',
'User-Agent': 'Bearer',
'Authorization': `token ${token}`
}
return axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers
})
}

Note: Depending on the API Authentication method of the integration, the Client might vary.

The Client object is based on axios, which makes it straightforward to use in functions, e.g.:

Client(token).get('/an-end-point')

The Event Object

A special event object is available to use in functions. This will help you retrieve user's access tokens (OAuth) or API Credentials (API Key, Basic Auth) as well as store and retrieve any data from a simple key/value store.

Access Token

If the integration is built on top of an OAuth API, you will retrieve the user's OAuth access token using:

event.context.auth.accessToken

API Key

If the integration is built on top of an API Key API, you will retrieve the API Key using:

event.context.auth.apiKey

Basic Auth

If the integration is built on top of a Basic Auth API, you will retrieve these credentials using:

event.context.auth.username
event.context.auth.password

Storing Custom Data

Integrations provide a simple key/value storage system, used as follows:

# Storing
event.store.save(referenceId, {key: "value"})
# Retrieving
event.store.find<{ key: string }>(event.params.referenceId)

The maximum item size is limited to 400 KB, which includes both the referenceId name binary length (UTF-8 length) and the value lengths (again, binary length).

Sending API Credentials

If an integration requires an API authentication mechanism, functions will have to send their credentials when calling the API.

Bearer handles the entire authentication logic and flow for you on various API authentication mechanisms (OAuth, API Key, Basic Auth etc.). We will securely manage API credentials and access tokens for you, abstracting the entire complexity behind authId and setupId.

Learn more about API authentication support in Bearer.

Setup Identifier

The Setup identifier, setupId, holds the API credentials. By default, they can be provided in the Settings page of each integration which returns a setupId.

If the integration relies on an API Key or Basic Auth mechanism, functions will require a setupId parameter by default. This way, the API credentials are retrieved by the event.context.auth object and are used to call the API.

Auth Identifier

The Auth identifier, authId, holds the user's OAuth access token that is returned at the end of the OAuth dance. Bearer stores and manages them for you. When you trigger the OAuth flow with a Connect Component, you will provide your own Auth Identifier.

If the integration relies on OAuth, functions will require an authId parameter by default. This way, the access tokens can be retrieved by the event.context.auth object and used to call the API.

Specifications

When you deploy an integration, Bearer automatically builds its specifications from functions that can be accessed through the Specifications tab:

As you can see, the functions are exposed and documented following the Open API 3 specification.

Functions are then called from your app using the integration clients.

Security

By default, functions can be integrated into an app from a Client-side or Server-side context using the integration clients.

Depending on the API authentication methods and your API usage, you might want to restrict the usage of function to a Server-side context only since it's almost impossible to entirely trust a client-side context.

To restrict it, simply use this property inside the function definition:

// Uncomment the line below to restrict the function to be called only from a server-side context
static serverSideRestricted = true

Manipulating Data

A function's main role is to work with data, so we thought it might be handy to remind you of some basic methods to handle most of your data transformation.

Below is a dataset we will use for our different methods:

const users = [
{ id: 1, name: 'Monica', last_name: 'Hall' },
{ id: 2, name: 'Dinesh', last_name: 'Chugtai' },
{ id: 3, name: 'Bertram', last_name: 'Gilfoyle' },
{ id: 4, name: 'Erlich', last_name: 'Bachman' },
{ id: 5, name: 'Richard', last_name: 'Hendricks' }
]
const bugs = [
{
userId: 2,
count: 100
},
{
userId: 3,
count: 43
}
]

Filtering

Filtering data from API endpoints is a must-have! Below are some examples filtering data using the filter method:

const filteredData1 = users.filter(user => user.id >= 3)
const filteredData2 = users.filter(({ id }) => id >= 3)
const filteredData3 = users.filter(user => {
if (user.id >= 3) {
return user
}
})

Remapping

Remapping data so the keys meet your app's expectations is a pretty common API data transformation. Below are examples using the map method:

const remap1 = users.map(user => {
if (user.id === 1) {
return {
id: user.id,
fullName: [user.name, user.last_name].join(' '),
VC: true
}
}
return {
id: user.id,
fullName: [user.name, user.last_name].join(' ')
}
})
const remap2 = users.map(user => ({
id: user.id,
fullName: [user.name, user.last_name].join(' ')
}))

Merging

Below is example of merging data using the map method:

const merge = users.map(user => ({
...user,
bug: ((bugs.find(bug => user.id === bug.userId) || {}) as any).count
}))

Reducing

Below are examples using the reduce method:

const reduce1 = bugs.reduce((acc, bug) => {
return (acc += bug.count)
}, 0)
const reduce2 = bugs.reduce((acc, bug) => (acc += bug.count), 0)