Getting Started with Salesforce Functions Locally. No license required! | Salesforce Developers Blog

Salesforce Functions is now Generally Available, so how can you start writing applications using Functions today? This blog post covers the first step: getting started with the local development experience.

You don’t need to have a license to get started with Salesforce Functions, you can use the Salesforce Developer Tools to create and test functions today. All you need is a Developer Edition Org and you’re ready to go.

The main limitation is that you will not be able to deploy to a Compute Environment, trigger a function from Apex, or deploy any Apex class that references the functions namespace. But you will still be able to create and start a function in your local environment and use the Salesforce SDK to access data from your org.

To get started, let’s create a Salesforce project using the new Salesforce CLI and integrate it with Functions. We’ll write a JavaScript function that retrieves video statistics and information from a YouTube playlist using the YouTube Data API v3 and stores them on Salesforce.

Getting started

Before creating the project, you’ll need to install the Salesforce CLI, Node.js, Docker Desktop, and other required tools. Take a look at the Development Environment setup guide for more information.

Note: Our team is currently working on a containerless local development experience, and it will be ready before January 31st, 2022.

We’ll use the new Salesforce CLI command: sf, which is part of our CLI unification project. It will be used to create a project and interact with the Functions environment. You don’t need to install anything new here, just update your Salesforce CLI by running:

sfdx update

Creating a project with the new Salesforce CLI

Let’s create a project from scratch by running:

sf generate project -n <project name>

Note: Be sure to name your project; I named it getting-started-functions, but feel free to be more creative.


Now that you have a project created, go ahead and log in to your Developer Edition org (or any other org with a DevHub), create a scratch org, and create a new custom object called Video__c to store the following properties:

  • Name (Title)
  • Video ID (Unique – External ID)
  • Description
  • View Count
  • Like Count
  • Dislike Count
  • Favorite Count
  • Comment Count

Note: For your convenience, I uploaded the source code of this project to a GitHub repository including the metadata for the custom object.

Use the new sf command to log in to your Developer Edition org by running:

sf login org -d -a my_dev_org

Note: This step is necessary to be able to integrate your Salesforce Function with an authenticated org.

Creating a Salesforce Function

Now it is time to create your first Salesforce Function. From the root of your Salesforce project, run the following command:

sf generate function -n youtubestats -l javascript

This will create a folder called functions and inside it will store all the functions for the project. If you navigate to that folder, you’ll find a youtubestats folder containing a Node.js project with the necessary boilerplate files to run and test a basic function.

Next, modify the index.js file and create a function that receives a YouTube Playlist ID as the payload. Using the YouTube Data API v3, retrieve its videos and statistics, then store the information on Salesforce using the Unit of Work pattern. This pattern allows you to manage CRUD operation transactions in a function.

One of the main advantages of Salesforce Functions is being able to use an open-source ecosystem of libraries to interact with external services, in this case, the YouTube API. For this purpose, you’ll use the googleapis client library from npm, the package manager for Node.js and JavaScript.

Let’s navigate to the functions/youtubestats folder and install the googleapis library by running the following two commands:

cd functions/youtubestats
npm install googleapis

Getting a Google API Key

In order to call the YouTube Data API v3, you’ll need to get a valid API Key. Please follow their getting started guide process to obtain one before continuing with this tutorial.

Note: Make sure you restrict this API Key to the YouTube Data API v3.

Now, you have everything you need, and you can start to build and run the function locally. First, replace the contents of the index.js file in the functions/youtubestats folder with the following code:

import { google } from "googleapis";
const youtube = google.youtube({
  version: "v3",
  auth: process.env.YOUTUBE_API_KEY
});

/**
 * YouTubeStats Function
 * This function receives a playlist id, retrieves its videos, and store each video statistics in Salesforce
 *
 * The exported method is the entry point for your code when the function is invoked.
 *
 * Following parameters are pre-configured and provided to your function on execution:
 * @param event: represents the data associated with the occurrence of an event, and
 *                 supporting metadata about the source of that occurrence.
 * @param context: represents the connection to Functions and your Salesforce org.
 * @param logger: logging handler used to capture application logs and trace specifically
 *                 to a given execution of a function.
 */
export default async function (event, context, logger) {
  logger.info(
    `Invoking YouTubeStats with payload ${JSON.stringify(event.data || {})}`
  );

  const playlistId = event.data.playlistId;

  if (!playlistId) {
    throw new Error("Missing playlistId parameter");
  }

  // 1. Retrieves the videos from a playlis using the googleapis library
  logger.info(`Retrieving PlaylistItems with ID = ${playlistId}`);
  const { data: playlistItems } = await youtube.playlistItems.list({
    part: "contentDetails,snippet",
    playlistId
  });

  // 2. Creates a list with video objects containing: title, description, and videoId
  const videos = playlistItems.items.map((video) => {
    return {
      videoId: video.contentDetails.videoId,
      title: video.snippet.title,
      description: video.snippet.description
    };
  });

  // 3. Retrieves the video statistics from every video using the googleapis library
  logger.info(`Getting Video Stats`);
  const { data: videoStats } = await youtube.videos.list({
    part: "statistics",
    id: videos.map((video) => video.videoId).join(",")
  });

  // 4. Maps the statistics to the list of video objects
  const stats = {};
  for (const stat of videoStats.items) {
    stats[stat.id] = stat.statistics;
  }

  // Creating a table to store the referenceId by Video ID
  const referenceTable = new Map();

  // 5. Uses the Unit of Work pattern to register the creation of each Video object into Salesforce
  const uow = context.org.dataApi.newUnitOfWork();

  for (const v of videos) {
    logger.info(`Creating Video with ID: ${v.videoId}`);
    const referenceId = uow.registerCreate({
      type: "Video__c",
      fields: {
        Video_ID__c: v.videoId,
        Name: v.title,
        Description__c: v.description,
        View_Count__c: stats[v.videoId].viewCount,
        Like_Count__c: stats[v.videoId].likeCount,
        Dislike_Count__c: stats[v.videoId].dislikeCount,
        Favorite_Count__c: stats[v.videoId].favoriteCount,
        Comment_Count__c: stats[v.videoId].commentCount
      }
    });
    referenceTable.set(v.videoId, referenceId);
  }

  // 6. Commits the Unit of Work as one single operation
  const response = await context.org.dataApi.commitUnitOfWork(uow);

  // 7. Maps the result containing the Salesforce id and the videoId
  const results = [];
  for (const [videoId, referenceId] of referenceTable.entries()) {
    const result = {
      id: response.get(referenceId).id,
      videoId
    };
    results.push(result);
  }

  return results;
}

Note: The source code for this file can be found in the GitHub repository.

This YouTubeStats Function does the following:

  1. Retrieves the videos from a playlist using the googleapis library
  2. Creates a list with video objects containing: title, description, and videoId
  3. Retrieves the video statistics from every video using the googleapis library
  4. Maps the statistics to the list of video objects
  5. Uses the Unit of Work pattern to register the creation of each Video object into Salesforce
  6. Commits the Unit of Work as one single operation
  7. Maps the result containing the Salesforce id and the videoId

Invoking a function locally

First, you’ll need to build and start a container running the function. You can do this by executing the following command in your local development environment from the functions/youtubestats folder:

sf run function start -e YOUTUBE_API_KEY=<INSERT API KEY>

sf run function start will run the function on a Docker container and it will expose a web process running by default in the following URL: http://localhost:8080.

The -e parameter sets an environment variable; for this example, you are setting the YOUTUBE_API_KEY you created earlier, which is needed to retrieve data from YouTube. If you take a look at the code, you are retrieving this value when calling: process.env.YOUTUBE_API_KEY.

Now that the function is running, you can invoke it from the CLI using sf run function. From a separate terminal, run the following command:

sf run function -l http://localhost:8080 -p '{"playlistId": "PLgIMQe2PKPSLNXs8AGpxgGbmk8Ylp_WTP"}'

In this example, I’m using the Introduction to Node.js for Salesforce Developers playlist from our Salesforce Developers channel.

Note: You can specify a target-org by setting the -o flag. Read more about sf run function in the developer documentation.

Here, you are sending the playlistId as part of the invocation payload with the -p parameter, and this needs to be encoded as a JSON string.

And that’s it — you have seen how a Salesforce Function can retrieve data from an external service and store it on a Developer Edition Org by using the local development experience. Go ahead and query the Video__c object in your scratch org to confirm that the data was stored successfully.

SELECT 
  Name, Description__c, Video_ID__c, View_Count__c, Like_Count__c, Dislike_Count__c, Favorite_Count__c, Comment_Count__c 
FROM Video__c
LIMIT 10

What’s next?

I have a challenge for you! What about creating a Salesforce Function to update the stats by using the Video ID? Take a look at the DataApi documentation to learn how to perform queries and CRUD operations from a function.

If you want to deploy this function to a Compute Environment and trigger it from Apex, you can Sign-up for a free trial. Don’t forget to review the documentation about Function Permissions and Deployment.

If you need help with the local development experience, or have any suggestions for future content around Functions, please visit the Salesforce Functions Trailblazer Community Group or the community-driven Salesforce StackExchange using the salesforce-functions tag.

Resources

About the author


Julián Duque is a Lead Developer Advocate at Salesforce. Connect with him on Twitter @julian_duque.

Stay up to date with the latest news from the Salesforce Developers Blog

Subscribe