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 in order 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

An example architecture that can be built with Jets is the traditional Web architecture:

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 handle(event, context)
  puts "hello world"
end

This is what AWS Lambda Ruby support would likely look like. The default handler is named handle. 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, aside from the ability to use Ruby they do not really add much value. Other classes like Controllers and Jobs 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. 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.

How Jets Works

AWS Lambda does not support Ruby yet, so how is this possible?

Simple. Ruby support is added by using a node shim. The shim is written a language that is natively supported by AWS Lambda and essentially calls out to Ruby.

Native Performance

What about speed? Is it slow?

Jets offers performance comparable to languages natively supported by Lambda. Jets loads Ruby into the Lambda execution context and reuses it, essentially giving Ruby native performance. Here’s a quick performance comparison:

Ruby function speed:

time curl -so /dev/null https://1192eablz8.execute-api.us-west-2.amazonaws.com/dev/ruby_example
real    0m0.164s
user    0m0.039s
sys     0m0.063s

Python function speed:

time curl -so /dev/null https://1192eablz8.execute-api.us-west-2.amazonaws.com/dev/python_example
real    0m0.178s
user    0m0.047s
sys     0m0.054s

In the case above, the Ruby function happened to be faster than the Python function. Generally, it’s a tie. More info here: Jets Native Performance.

Debugging Ruby Errors

What about debugging? Is it complicated?

You might think that the shim can complicate debugging but Jets makes the shim transparent. You can debug your application just like you would any application written in a language that’s natively supported by AWS Lambda. Jets surfaces Ruby errors directly to the Lambda console. Here’s an example of Ruby code throwing an intentional error:

class PostsController < ApplicationController
  # ...
  def ruby_example_error
    INTENTIONAL_RUBY_ERROR
    render json: {message: "hello from ruby #{RUBY_VERSION}"}
  end
end

Here’s what the stack trace appears like in the Lambda Console.

There is no mental context switching. You stay in Ruby-land the entire time.

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. Note, the example records automatically get deleted and reseeded daily.

More to Cover

There’s lots more to cover, including Rails Support. Here’s a tutorial blog post: Jets Mega Mode: Run Rails on AWS Lambda. Here’s also a summary of what Jets has to more 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.
  • 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