The Blog

Our latest insights, opinions and general musings as we float about on the vast ocean of tech we call home.

Building a Slack bot with a Lambda Function URL

Building a Slack bot with a Lambda Function URL

Lambda function URLs are an exciting new release, and the options for use cases are vast. As a result, we will delve into AWS Lambda function URLs in more detail. This blog post will look at their pros and cons, how we built our Slack bot with a Lambda URL and outline scenarios where we believe they would be a good alternative. Lambda function URLs allow you to create a unique URL directly on a Lambda without having to configure another service such as API Gateway. As a result, they have some intriguing benefits over a traditional setup but may not always be the best solution for your own projects.

Lambda Function URL vs API Gateway URL

Benefits of Function Urls:

  • Really quick and easy set up for endpoints with a single-use case (For example, Slack bots or webhooks)
  • There is less latency than using API Gateway
  • Good for single purpose Lambdas as it creates a mono-Lambda encapsulating all HTTP methods in one Lambda
  • There is a much larger timeout of 15 minutes vs 29 secondss
  • No extra cost associated with Lambdas URLs, whereas API Gateway has its own pricing structure on top of the Lambdas invocation
  • CORS configurable
  • URLs can be mapped to the latest version or alias of your Lambda, this is good for testing the latest version and using safe deployments with gradual traffic shifting.

Disadvantages:

  • No custom domains can be created directly on the Lambda URL however they can be implemented via cloudfront if they are needed
  • With the mono-Lambda approach, where all request types are routed to a single Lambda it can make debugging much harder, as the logs for all methods are contained within the same CloudWatch log, making them harder to sift through. It also means you cannot be as specific with your permissions compared to having the methods split out into separate Lambdas
  • There is no caching or throttling of traffic which happens automatically with API Gateway
  • Can only use IAM for authorizers to secure the Lambda function URLs. With API Gateway you have more options such as a custom Lambda or Cognito authorizer.

In our example we chose to use a function URL. This reduces the amount of set up required.As a result, we ensured we didn't incur any additional costs over and above the Lambda invocations.

Serverless stack set up

If you are looking to build your own, this repo contains all the code examples for our Slack bot. You are welcome to clone and deploy it yourself or follow the steps below to build your own.

The Orange Jellyfish starter kit will also enable you to scaffold a new Serverless application quickly and effectively, using the Serverless CLI.

npx serverless create --template-url https://github.com/orangejellyfish/serverless-starter --path your/local/path

If you have previously created a Lambda with slash command integration from our Building a Serverless Slack bot post then this can be also be updated to a Lambda URL.

Setting up a Lambda function URL

The first step in setting up a Lambda function URL is ensuring that you are using Serverless Framework v3.12.0 or above. This is crucial as it supports the new functionality. Then you are able to create a new function such as lambda-url-slack-hook.

your-app/
└── src/
    └── functions/
            └── lambda-url-slack-hook/
                ├── index.js
                └── index.yml

Now our index.yml for our slack hook looks very simple:

lambdaUrlSlackHook:
  handler: src/functions/lambda-url-slack-hook/index.default
  url: true

One difference between using API Gateway and the Lambda Url is that API Gateway automatically decodes requests for you. As we’re not using that here we have to decode the event body in the Lambda.

export const lambdaSlackHook = async function lambdaSlackHook(event) {
 const { body } = event;
 
   const buff = Buffer.from(body, 'base64');
   const text = buff.toString();
   const data = new URLSearchParams(text);
 
 
 return {
   statusCode: 200,
   headers: {
     'Content-Type': 'application/json',
   },
   body: JSON.stringify({
     response_type: 'ephemeral',
     text: data.get('text'),
   }),
 };
};
 
export default lambdaSlackHook;

Deploy this by running npm run deploy

If you have not already linked this to a slash command in Slack, then check out our previous blog post for a step-by step guide on how to do so. The most notable difference is the location of the function URL. It can now be found in the Lambda dashboard rather than the API Gateway. The Slack bot can now echo what you type.

Find Lambda URL Finding Lambda URL in AWS console

Creating a Pub Suggestion Slack bot

In order to locate nearby places, we use Google Places API. You can sign up for this service by creating an account on the Google console. Next, follow Google's API console help steps to enable the Places API with an API key.

Next, click Enable, this API comes with a free tier of $200 usage per month.

Enable Google Places API Enable Google Places API in Google Cloud console

Finally, retrieve your API key from the credentials page. In order to keep the key secure, we don't want to deploy it with the serverless stack. To combat this, we use the Parameter Store in AWS Systems Manager to store this value.

  • Create a parameter called /dev/google-maps-api-key
  • Select secure string
  • Paste key into value text area

Create parameter in AWS Systems Manager Create parameter in AWS Console

Add the google maps key to your index.yml so that your Lambda is able to access the value.

slackHook:
 handler: src/functions/slack-hook/index.default
 url: true
 
 environment:
   GOOGLE_MAPS_API_KEY:
     ${ssm:/${opt:stage, self:provider.stage}/google-maps-api-key}

In order to locate a pub near the users, the Google Maps API needs to be able to pinpoint their latitude and longitude. We can find this by using the node-postcodes.io package from npm. This library takes a postcode and convert it into latitude and longitude values. Therefore, the slash command will look like this /<your-command> <postcode>

import { Client } from '@googlemaps/google-maps-services-js';
import postcodes from 'node-postcodes.io';
 
const client = new Client({});

export const lambdaSlackHook = async function lambdaSlackHook(event) {
 const { body } = event;

 const buff = Buffer.from(body, 'base64');
 const text = buff.toString();
 const data = new URLSearchParams(text);
 
 let postcodeData;
 try {
   postcodeData = await postcodes.lookup(data.get('text'));
 } catch (err) {
   console.log(err);
 }
 
 if (postcodeData.status === 404 || !postcodeData) return inputError;
 
 let r;
 if (postcodeData) {
   try {
     r = await client.placesNearby({
       params: {
         location: { lat: postcodeData.result.latitude, lng: postcodeData.result.longitude },
         key: process.env.GOOGLE_MAPS_API_KEY,
         type: 'bar',
         rankby: 'distance',
       },
       timeout: 1000, // milliseconds
     });
   } catch (err) {
     console.log('err', err);
   }
 }
 
 return {
   statusCode: 200,
   headers: {
     'Content-Type': 'application/json',
   },
   body: JSON.stringify({
     response_type: 'ephemeral',
     text: `${r.data.results[0].name}, ${r.data.results[0].vicinity}`,
   }),
 };

Once you deploy this version, the Slack bot will respond with the name and address of a nearby pub!

Command for Slack bot Call Slack bot with postcode parameters

Response of Slack bot Response of pub nearby Slack bot

Conclusions

If you're looking to explore serverless without having many features of API gateway and other frameworks to contend with, Lambada URL might be the perfect choice for you.

Additional reasons to select Lambada URLs include the following:

  1. If you already have a backend framework such as Express then you may not need the additional filtering, processing and routing options available with API Gateway.

  2. If you are just getting started with serverless this is a quick, relatively easy, cheaper way to explore what it has to offer.

  3. The longer timeout periods mean they will be useful for any heavy data processing you might want to do which will take longer than 29 seconds.

  4. Good for simple webhooks or single function microservices in circumstances where you do not want or need API Gateway or another framework.

Next Steps

  • Add radius parameter and select a random result instead of always choosing the nearest
  • Explore Google's APIs to create other interesting Slack bot examples

Want to know more?

Get in touch.