Writing AWS Lambda Functions in Ruby

AWS Lambda lets you run your code without worrying about the nuts and bolts of server management or scaling. You might even say it's "serverless." In this article, Jeffrey Morhous shows us how to get started writing Lambda functions in Ruby.

AWS Lambda allows us to set up scalable functions while minimizing overhead. Instead of writing, hosting, and maintaining an entire Ruby on Rails app, we can use Lambda functions to respond to individual events independently. This article will bring you from an AWS newcomer to writing Ruby in your own Lambda functions.

Lambda allows you to run code in response to events without managing servers. This event-driven architecture ensures that you only pay for your code while it's working for you, not while it's idling. While Lambda is most commonly used to respond to events within the AWS ecosystem, such as deleting a file in an S3 Bucket, it can also be configured to act as an API in conjunction with AWS API Gateway.

One could use Lambda to automate tasks based on time, such as Cron Jobs, or in response to events. This could extend into data processing, often with other AWS services, such as DynamoDB, or even into a scalable API. For example, the following diagram from Amazon's documentation shows a simple architecture where a mobile client makes an HTTP request, presumably to perform some CRUD action on the database. API Gateway routes the request to the corresponding Lambda function (much like Rails routes HTTP requests to a controller), and it performs the business logic and communicates with AWS DynamoDB to fulfill the request.

A diagram of API Gateway and Lambda flow

Because our code is run in a container, Amazon's infrastructure automatically puts the function in a sort of sleep mode when it is not being used. When a new request comes in, the container must start from a cold state, which can increase execution time by up to 2000ms. This can not only degrade your quality of service in terms of response time but also cost you money. For this reason, using Lambda for a time-sensitive task, such as sending user emails or responding to UI events, could create a bottleneck. You could mitigate this problem by scheduling AWS CloudWatch to make periodic requests to keep the function 'warm', but this would increase hosting costs.

Why Use AWS Lambda

Amazon Web Services (AWS) is the most popular cloud computing service in the world. It is simple enough for independent developers to use it but powerful and scalable enough to serve as infrastructure for some of the world's largest companies.

AWS Lambda is one such offering from Amazon that serves as a 'serverless compute service', which allows us to run code only in response to events. This reduces overhead and allows us to programmatically react to something, such as an API call, without hosting an entire server or building an entire app.

For the right use case, this serverless architecture can save a fortune in costs. Pricing is calculated with cost per request and cost per duration of compute time. After the first one million requests in a given month, pricing is $0.20 per one million requests plus $0.0000166667 for every GB-second of compute time the function uses. As you can imagine, this rate-based pricing makes it remarkably cheap to run a small service on Lambda while still maintaining the option of scaling infinitely at the same rate.

Ruby developers can benefit from this cloud service by using it as a stateless way to react to API calls or even other AWS events.

Setting Up a Trial AWS Account

AWS provides a rather generous free tier that includes one million free requests and up to 3.2 million seconds of compute time per month with AWS Lambda! If you already have an account, you can skip this step, but setting one up is very simple.

Head on over to sign up here and fill in your information to get started! A screenshot of the AWS account sign up page

You'll be prompted to fill in more information, including account type and contact information, and you will need to enter credit/debit card information to cover any usage outside of the free tier. Our simple usage of Lambda is included in the free tier, but if you're worried about accidental overages, you can set up a budget to control your usage and prevent unexpected billing.

Creating a Lambda Function with Ruby

Once you are registered, hover over products, then compute, and select Lambda.

A screenshot of selecting AWS Lambda from Products

Once you're in the Lambda Console (you might have to hit 'get started'), you should see an empty list of functions with an orange button for "Create Function". As you might have guessed, we'll be pressing that orange "Create Function" button.

A screenshot of the Lambda Console

Then, we'll choose "Author from Scratch," as there are no template functions for Ruby yet, and working from scratch is the best way to learn.

You should name your function appropriately and select the Ruby version with which you want to work. I called mine FizzBuzz, after the famous programming problem.

You may have to double-click on the lambda_function.rb file in the navigation pane to make the source code appear.

The function comes prewritten and is titled lambda_handler. As it stands, when it is invoked the function will return a 200 response with the text "Hello From Lambda!", as you can see below. The default lambda function

Using the Test Trigger for the Lambda Function

As discussed in the intro, Lambda functions can be triggered in a variety of different ways. One of the most common is through an API call. This, however, requires setting up AWS API Gateway, which is a bit outside of the scope of this article. API Gateway works wonderfully with Lambda to create APIs with very little configuration and infinite scalability.

Fortunately for us, it's not too difficult to simulate an API call through the function console, and a test event is already queued up for us.

Underneath the function overview is a toolbar that currently has "Code" selected. As shown in the last screenshot, right next to "Code" is a tab titled "Test". Go ahead and select "Test", and you'll see the Test console appear in the place of the code source.

A screenshot example test event

The pre-configured test event is a JSON object that gets sent to the Lambda function as the event object, which the function takes as a parameter. If you were calling an endpoint through API Gateway, this would be the body of the request.

Because the placeholder Lambda function doesn't read the event, it doesn't much matter what we send as the body. As such, we can proceed as written by tapping the orange "Invoke" button.

If you've set up the function correctly, you should see a green box titled "Execution Succeeded", which you can expand to see the JSON response from your function.

Writing FizzBuzz

Now that we've confirmed our Lambda function is working correctly, we can customize it with our own custom Ruby code. In this example, we'll have our Lambda function respond to the famous FizzBuzz problem. FizzBuzz is an interview favorite, and the prompt goes something like this

Write a program that prints the numbers from 1 to 50. But, for multiples of three, print “Fizz” instead of the number, and for multiples of five, print “Buzz”. For numbers that are multiples of both three and five print “FizzBuzz”.

Replace the code in your Lambda function with my FizzBuzz solution -

require 'json'

def lambda_handler(event:, context:)
    max_val = event['max_val']
    textResponse = ''
    1.upto(max_val) do |i|
      if i % 5 == 0 and i % 3 == 0
        textResponse += "FizzBuzz"
      elsif i % 5 == 0
        textResponse += "Buzz"
      elsif i % 3 == 0
        textResponse += "Fizz"
        textResponse += i.to_s
    { statusCode: 200, body: JSON.generate(textResponse) }

This is a little bit of a spin on the usual FizzBuzz. Instead of just looping through to 50, we are looping through the number that is sent with the event. The event object is passed in like a dictionary, so asking for event[max_val] returns the value at the max_val key. Finally, instead of printing out the FizzBuzz solution, we just append it to a String that we return as a JSON object.

Next, make sure you hit the Orange "Deploy" button to update your Lambda function with the new code.

After you've written the solution and deployed it, hop back on over to the Test console. Change your test event to just pass in a max_val key/value pair. It should look like this:

A screenshot of the final test event

The JSON is simply

  "max_val": 70

Finally, hit "Invoke" to run your test! You can expand the result to show that the function read the input and returned the result of the FizzBuzz problem.

Easier Developing

While working in the web console has been convenient for learning purposes, being restricted to it would be a nightmare. One of the most popular ways to develop with AWS Lambda is to use the CLI, which stands for Command Line Interface.

Once you create the right permissions through IAM roles, install the CLI on your machine and configure it with your credentials. Then, you can use the Lambda CLI to test your function as you develop locally (in your IDE of choice) and deploy your changes through the command line.

Another popular development method is through frameworks, such as Serverless. Serverless handles much of the cumbersome configuration that AWS is plagued with, including setting up API Gateway should you need it for your function. Serverless also enhances logging, makes Ruby Gem management easier, and allows the configuration of CI/CD, so you can automatically deploy from your repository based on pipelines.

Without using Serverless or a similar framework, including Ruby Gems requires jumping through some hoops. You'll have to expand your function to involve a folder, and then create a .bundle/config file in your root directory that has the following initialization code in it to tell bundler where to put the gems you install:

BUNDLE_PATH: "vendor/bundle"

Then, you can use the CLI to bundle add the gems you need and require them directly in your code.

Limitations of AWS Lambda

Congratulations, you just wrote your first AWS Lambda function in Ruby! Previously, the only feasible solution to run an API with Ruby was often to create an entire Rails app. Now, you can create endpoints one at a time, skipping all the bloat that comes with a full application. Lambda bills for usage in microseconds, so it can be remarkably cheap for quick solutions. Furthermore, taking advantage of this cloud offering allows us to operate at low cost but still scale incredibly quickly. AWS is the reason Netflix works during peak hours without having to pay for constant maximum-capacity server bandwidth.

Despite all the wins, AWS Lambda is not a catch-all solution. It won't function as an API on its own and is limited in its state capabilities. Lambda functions can't take more than 15 minutes to run, and memory is restricted to 6GB. It's also a bit of a challenge to test Lambda functions, as you'll often have to duplicate your entire infrastructure in a separate environment.

If you have an app or feature that doesn't need to be running 24/7, then Lambda might be a good fit. If your function needs to maintain some kind of state or even update data outside of the AWS ecosystem, then Lambda is not a good fit. Lambda also defaults to 1,000 concurrent connections, so an application that consistently sees more use than that would be very unreliable on Lambda. Essentially, Lambda is best used in short-lived event-driven automation tasks that don't require a persistent state.

Lambda functions can be a powerful tool, especially in the hands of a Ruby developer like you.

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Jeffery Morhous

    Jeff is a Software Engineer working in healtcare technology using Ruby on Rails, React, and plenty more tools. He loves making things that make life more interesting and learning as much he can on the way. In his spare time, he loves to play guitar, hike, and tinker with cars.

    More articles by Jeffery Morhous
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial
    “Wow — Customers are blown away that I email them so quickly after an error.”
    Chris Patton, Founder of Punchpass.com
    Start free trial