How to create and use BCMS Functions?

By Branislav Vajagić
Read time 2 min
Posted on October 28, 2022
Share it on:
BCMS Functions

The easiest way to add additional functionality to the BCMS backend is by creating BCMS Functions. Simply said, they are JavaScript functions that can be executed by sending an HTTP request to the BCMS backend. We could say that BCMS Functions are similar to webhooks. Once a Function is created, it will be available at POST: /api/function/{MY_FUNCTION}. The best way to explain this is to show you an example.

Create BCMS Function

For a start, we will use local BCMS, and latter, we will see how to deploy a Function to a live BCMS.

Creating a Function is as simple as creating a file inside ./src/functions and adding the configuration, something like in the snippet below.

// File: ./src/functions/hello-world.ts

import { createBcmsFunction } from '@becomes/cms-backend/src/function';

export default createBcmsFunction(async () => {
  // Do some setup if needed.
  // ...

  return {
    config: {
      name: 'hello-world',
      public: true,
    },
    async handler() {
      return {
        message: 'Hello world!',
      };
    },
  };
});
          
  • config.name - Name of the Function. This name will be encoded to URI and the Function will be available at /api/functino/{function-name}.

  • config.public - Flag which defines if the Function if public or private.

  • handler - Your custom logic will be executed every time that the Function is called.

Let's explain what is happening here. We are exporting a default function from which we are returning a configuration object. In the Function configuration, you can set the name (this will determine on which URL the function will be available) and if the Function is public or not (this will determine if authentication is required to call the Function).

Now, if we make an HTTP request to /api/function/hello-world, this is what we get:

POST http://localhost:8080/api/function/hello-world

HTTP/1.1 200 OK
Server: nginx
Date: Fri, 28 Oct 2022 07:43:01 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 52
Connection: close
X-Powered-By: Express
Access-Control-Allow-Origin: *
ETag: W/"34-YxEPdGOTXp8JbDPw+kjRltr5odQ"

{
  "success": true,
  "result": {
    "message": "Hello world!"
  }
}
          

Handling requests

As you saw, creating a BCMS Function is very easy, but what happens if we need to send some data to a Function? That is also extremely easy! Pure ExpressJS request is piped to the handler method.

To show this, let's modify the Function from about to accept the name and return a string "Hello {name}".

import { createBcmsFunction } from '@becomes/cms-backend/src/function';

export default createBcmsFunction(async () => {
  return {
    config: {
      name: 'hello-world',
      public: true,
    },
    async handler({ request }) {
      return {
        message: `Hello ${request.body.name}!`,
      };
    },
  };
});
          

Now, if we send a request, this is what we get:

POST http://localhost:8080/api/function/hello-world
Content-Type: application/json

{
  "name": "Demo"
}

HTTP/1.1 200 OK
Server: nginx
Date: Fri, 28 Oct 2022 08:14:14 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 51
Connection: close
X-Powered-By: Express
Access-Control-Allow-Origin: *
ETag: W/"33-brM8jcikN+r07uCfpt+xnoe90qk"

{
  "success": true,
  "result": {
    "message": "Hello Demo!"
  }
}
          

Handling errors

As you can see, in the example above, we are not checking if property name is present in the body. If the name is important to the Function logic, we would like to check if it exists, and if it does not, we would like to throw an error to the client.

The simplest way to do this is by throwing a pure JS error:

if (!request.body.name) {
  throw new Error('Property "name" is required in the body.');
}
          

If we now send a request without a body or property name, this is what we get:

POST http://localhost:8080/api/function/hello-world
Content-Type: application/json

HTTP/1.1 500 Internal Server Error
Server: nginx
Date: Fri, 28 Oct 2022 08:24:44 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 71
Connection: close
X-Powered-By: Express
Access-Control-Allow-Origin: *
ETag: W/"47-isTeGboeSQ6qHuu4KdNk68ySoqk"

{
  "success": false,
  "result": "Property \"name\" is required in the body."
}
          

As you are able to see, we got the error with our message and status code 500. This is completely fine, and for a simple function, you can do this.

A better solution is to return an error with status code 400 since the error is not internal, but the client did something wrong. For that, you can use Error Handler, which is piped to the handler method:

if (!request.body.name) {
  throw errorHandler.occurred(400, {
    error: 'Property "name" is required in the body.',
  });
}
          

Now, if we send a request to the Function, we are getting a much better response:

POST http://localhost:8080/api/function/hello-world
Content-Type: application/json

HTTP/1.1 400 Bad Request
Server: nginx
Date: Fri, 28 Oct 2022 08:33:11 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 81
Connection: close
X-Powered-By: Express
Access-Control-Allow-Origin: *
ETag: W/"51-CIHDEUWtFrODGMsW8wSP8BLmleE"

{
  "result": {
    "error": "Property \"name\" is required in the body."
  },
  "success": false
}
          

Create and call a private Function

Until now, all examples that we showed were using a public Function, accessible to anyone. What if you want to make a private Function accessible only to authenticated sources?

You can create your custom authorization, or you can use the authorization provided by the BCMS. To interact with private BCMS Functions, we will use BCMS Client, a tool created to abstract communication and authentication with the BCMS.

All that we need to do to make a Function private is to set the public flag to false. To call this function, we need to create a new instance of the BCMS Client and call our Function:

import { createBcmsClient } from '@becomes/cms-client';

const client = createBcmsClient({
  cmsOrigin: '{CMS_ORIGIN}',
  key: {
    id: '{API_KEY_ID}',
    secret: '{API_KEY_SECRET}',
  },
});

async function callFunction() {
  const result = await BCMSClient.function.call('hello-world', {
    name: 'Demo',
  });

  console.log(result);
  /**
   * OUTPUT -----
   * 
   * {
   *  "result": {
   *    "error": "Property \"name\" is required in the body."
   *  },
   *  "success": false
   * }
   */
}
          

Deploying functions

We saw how to create BCMS Functions and run them locally on the BCMS. But how to get the Functions you wrote to live BCMS? This is also very simple!

In your BCMS project, just run npm run bundle. This will bundle and prepare all your resources to be deployed to the live BCMS. Once that is done, run npm run deploy. You will be asked to select a BCMS Instance to which you want to deploy your code. After that, your code will be live, and you can interact with it.

  • It takes a minute

  • Free support

  • 14 days free trial

Create your account