Update 2018/12/12: Official Ruby Support was announced at AWS re:Invent 2018 on Nov 29! Jets has switched over to it: Official AWS Ruby Support for Jets 🎉. This article has been updated to reflect official Ruby support, but video in the post is out-of-date and will be updated in time.

Ruby on Jets is a framework that allows you to build serverless applications in a beautiful language: Ruby. It includes everything needed to build and deploy applications to AWS Lambda. I love working with Rails, Ruby and AWS; and wanted to work with something similar in the serverless world. So I built Jets.

It is key to understand AWS Lambda and API Gateway to understand Jets conceptually. Jets maps your code to Lambda functions and API Gateway resources.

  • AWS Lambda provides Functions as a Service. It allows you to upload and run functions without worrying about the underlying infrastructure.
  • API Gateway is the routing layer for Lambda. It is used to route REST URL endpoints to Lambda functions.

Example Architecture

Many architectures can be built with Jets. Here’s an example traditional Web architecture that can easily be accomplished with Jets:

With Jets, you write code and Jets turns the code into AWS Lambda functions and API Gateway resources.

Functions

Jets supports writing simple AWS Lambda functions with Ruby. You define them in the app/functions folder. A function looks like this:

app/functions/simple.rb:

def handler_function(event:, context:)
  puts "hello world"
end

The default handler is named handler_function. The lambda function shows up in the Lambda console like this:

You can run the function in the AWS Lambda console and see the results:

Though simple functions are supported by Jets, they do not add much value as other classes like Controllers and Jobs. These other classes add many conveniences and are more powerful to use. We’ll cover them next.

Controllers

Here’s the first example of Jets code, a controller:

app/controllers/posts_controller.rb:

class PostsController < ApplicationController
  def index
    # renders Lambda Proxy structure compatiable with API Gateway
    render json: {hello: "world", action: "index"}
  end

  def show
    id = params[:id] # params available
    # puts goes to the lambda logs
    puts event # raw lambda event available
    render json: {action: "show", id: id}
  end
end

If you’re familiar with Rails and Sinatra, this will look familiar. Helper methods like params provide the parameters from the API Gateway event. The render method renders a Lambda Proxy structure back that API Gateway understands.

Jets takes each controller’s public methods and turns them into Lambda functions. Here are the functions in the Lambda console:

Routes

Here’s what a routes file could look like:

config/routes.rb:

Jets.application.routes.draw do
  get  "posts", to: "posts#index"
  get  "posts/new", to: "posts#new"
  get  "posts/:id", to: "posts#show"
  post "posts", to: "posts#create"
  get  "posts/:id/edit", to: "posts#edit"
  put  "posts", to: "posts#update"
  delete  "posts", to: "posts#delete"
end

Jets takes the routes file, creates the corresponding API Gateway resources, and associates them with Lambda functions. Here are the routes in the API Gateway console:

Jobs

Jets also supports asynchronous jobs that work outside the web request-response cycle. Job code looks like:

app/jobs/hard_job.rb:

class HardJob < ApplicationJob
  rate "10 hours" # every 10 hours
  def dig
    {done: "digging"}
  end

  cron "0 */12 * * ? *" # every 12 hours
  def lift
    {done: "lifting"}
  end
end

The code above creates Lambda functions and CloudWatch event rules to handle the scheduling of work.

You can check for the job functions in the Lambda console:

You can also see the associated CloudWatch Event Rule in the CloudWatch console:

Project structure

Here’s what a Jets project structure looks like.

.
├── app
│   ├── controllers
│   ├── helpers
│   ├── javascript
│   ├── jobs
│   ├── models
│   └── views
├── bin
├── config
├── db
├── public
└── spec

We have the traditional MVC folders: app/models, app/views, and app/controllers. The config folder contains your application’s configuration settings. Further explanation for each folder is provided on the Project Structure docs.

Quick Start

Here are commands that generate a CRUD app to get you started:

gem install jets
jets new demo
cd demo
jets generate scaffold Post title:string
vim .env.development # edit with local db settings
jets db:create db:migrate
jets server

The jets server command starts a server that mimics API Gateway so you can test locally. Open http://localhost:8888/posts and test out the CRUD site created from the scaffold.

When you’re ready, adjust your .env.development.remote with an RDS database and deploy to AWS Lambda.

$ vim .env.development.remote # adjust with remote db settings
$ jets deploy
API Gateway Endpoint: https://puc3xyk4cj.execute-api.us-west-2.amazonaws.com/dev/

You should see something like this:

Lambda Functions:

API Gateway:

The app itself:

Here’s a live Demo of this tutorial. Note, the example records automatically get deleted and reseeded daily.

Other Live Demos

Here are additional Live Demos of Jets applications:

More examples are in the jets-examples repo.

Extra Environments

An interesting benefit of running applications on AWS Lambda is that you only get charged for actual requests. So extra environments are likely in the AWS free tier. You could do this:

JETS_ENV_EXTRA=2 jets deploy
JETS_ENV_EXTRA=3 jets deploy
...
JETS_ENV_EXTRA=8 jets deploy

You essentially get unlimited free environments, each of them taking a few minutes to provision.

Binary Gems

With Serverless, you will might run into the quirky binary gem issue. Most gems are pure Ruby code and can be used as-is on AWS Lambda. However, some gems like nokogiri use compiled native extensions. So you must compile these binary gems to the Lambda target architecture for them to work. Jets uses Lambda Gems to resolve this and makes for a much more seamless and pleasant developer experience. The Beta for Lambda Gems is has been open. Early signups for Lambda Gems will receive a special offer for their support.

More to Cover

There’s lots more to cover, including Rails Support. Here’s a tutorial blog post: Jets Afterburner: Rails on AWS Lambda. Here’s also a summary of what Jets has to offer:

  • Prewarming Support: Remedies the Lambda cold start problem.
  • Local Server: A server which mimics API Gateway that allows you to test locally.
  • Jets Call: Remote and local testing of the functions directly.
  • REPL Console: A REPL jets console that allows you to test things out in an interactive shell quickly.
  • Database Support: Supports MySQL and PostgreSQL via ActiveRecord. It also support DynamoDB. An ActiveRecord db and DyamoDB can be used together in the same app.
  • Rails Support: Add an Rails app with little effort: Run Rails on AWS Lambda Tutorial.
  • Binary Support: Upload images and files easily.
  • Polymorphic Support: Ability to write your lambda functions in other languages like python or node for certain uses cases.
  • Function Properties: Allows you to set Lambda function properties application-wide, at the class-level, or individually at the function level.
  • Custom Associated Resources: Allows you to extend Jets and define your own AWS resources and associate them with Lambda functions.
  • Shared Resources: Allows you to extend Jets and define your own general purpose AWS resources.
  • IAM Policies: Fine grain control over IAM policies associated with your Lambda functions.
  • Deployment: A deploy command that handles the mundane task of packaging and deploying your code to AWS Lambda.

For more info check out the documentation site rubyonjets.com and the CLI reference.

Hoping to post more on Jets in the future. Feel that I’ve been lucky enough to be able to combine a lot of learnings from over the years to make Jets. Hope you like Jets and give it a try. Also if you find Jets interesting, please give it ⭐️ it on GitHub. 👍

You Might Also Like