The way you enjoy Amplify with Ruby

·

7 min read

Introduction

Recently, I've been playing with Amplify to build web apps and it's really good when you want to quickly build the backend.

Here's what I like about it:

  1. Just by answering the questions in Amplify's CLI in the daisy chain manner like APIGW>Cognito>Lambda>DynamoDB, it will create everything you typically need to create an app. If you choose NodeJS for Lambda, it will even create functions that does CRUD for DynamoDB via APIGW.
  2. Also, it sets up IAM roles between the services together, so you don't need to set up permissions individually.

On the other hand, each setting is rough, and you may need to configure the details individually outside of amplify.

Where is Ruby?

However, as a Rubyist who is in the level to choses Ruby where possible to create a quick and dirty hobby app, I am not motivated to use the Amplify CLI if I can't choose Ruby as a language.

The moment I saw this in amplify function add, I was even more depressed that Ruby lost to .NET really!? (Amplify version is 4.45.2)

? Choose the runtime that you want to use: (Use arrow keys)
  .NET Core 3.1 
  Go 
  Java 
❯ NodeJS 
  Python

Let's use Ruby

Okay, let's make Ruby working then. Since Amplify doesn't support Ruby, we'll have to use Ruby without Amplify knowing about it. For now, we'll pretend to use Node as the runtime and replace it with Ruby later.

amplify init

init for an web app

$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project todo
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using none
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  npm run-script build
? Start Command: npm run-script start
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS profile

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html

? Please choose the profile you want to use default

amplify api add

Start with adding API. From here you will create from API, Lambda and all the way through DynamoDB in a daisy chain manner. Still keep pretending to use Node here.

$ amplify api add
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: todoAPI
? Provide a path (e.g., /book/{isbn}): /tasks
? Choose a Lambda source Create a new Lambda function
? Provide an AWS Lambda function name: todoFunc
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: CRUD function for DynamoDB (Integration with API Gateway)
? Choose a DynamoDB data source option Create a new DynamoDB table

Welcome to the NoSQL DynamoDB database wizard
This wizard asks you a series of questions to help determine how to set up your NoSQL database table.

? Please provide a friendly name for your resource that will be used to label this category in the project: todoDB
? Please provide table name: todoTable

You can now add columns to the table.

? What would you like to name this column: task-id
? Please choose the data type: string
? Would you like to add another column? No

Before you create the database, you must specify how items in your table are uniquely organized. You do this by specifying a primary key. The primary key uniquely identifies each item in the table so that no two items can have the same key. This can be an individual column, or a combination that includes a primary key and a sort key.

To learn more about primary keys, see:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey

? Please choose partition key for the table: task-id
? Do you want to add a sort key to your table? No

You can optionally add global secondary indexes for this table. These are useful when you run queries defined in a different column than the primary key.
To learn more about indexes, see:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.SecondaryIndexes

? Do you want to add global secondary indexes to your table? No
? Do you want to add a Lambda Trigger for your Table? No
Successfully added DynamoDb table locally

Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration

? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? No
Successfully added resource todoFunc locally.

Next steps:
Check out sample function code generated in <project-dir>/amplify/backend/function/todoFunc/src
"amplify function build" builds all of your functions currently in the project
"amplify mock function <functionName>" runs your function locally
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
Succesfully added the Lambda function locally
? Restrict API access No
? Do you want to add another path? No
Successfully added resource todoAPI locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

Long Q&A but the points are:

? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: CRUD function for DynamoDB (Integration with API Gateway)

You may feel you can select whatever for the function template but it is easier to chose CRUD function for DynamoDB (Integration with API Gateway) as Amplify takes care of the role/permission for the function to access to the DynamoDB

? Restrict API access No

You can add authentication setting later. Let's select No for now to avoid trouble.

amplify push

Let's push and sync with the cloud here.

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name | Operation | Provider plugin   |
| -------- | ------------- | --------- | ----------------- |
| Storage  | todoDB        | Create    | awscloudformation |
| Function | todoFunc      | Create    | awscloudformation |
| Api      | todoAPI       | Create    | awscloudformation |

Replace the runtime with Ruby's

Now, you will replace the runtime and hander to Ruby's with a line of command.

$ aws lambda update-function-configuration --function-name todoFunc-dev --runtime ruby2.7 --handler lambda_function.lambda_handler
{
    "FunctionName": "todoFunc-dev",
    "FunctionArn": "arn:aws:lambda:us-east-2:578170637269:function:todoFunc-dev",
    "Runtime": "ruby2.7",
    "Role": "arn:aws:iam::578170637269:role/todoLambdaRolee9f33d88-dev",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 11369236,
    "Description": "",
    "Timeout": 25,
    "MemorySize": 128,
    "LastModified": "2021-03-21T01:54:56.507+0000",
    "CodeSha256": "3avEYotMLxKa7lXTN4P0CUPACv4ds14mjePO2IEzKms=",
    "Version": "$LATEST",
    "Environment": {
        "Variables": {
            "ENV": "dev",
            "REGION": "us-east-2"
        }
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "f89f9d07-cf82-4f68-a51e-ff2246962fe6",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}

Now what?

With the procedure above, your function is now out of Amplify's management. Your will manage it separately.

$ amplify status

Current Environment: dev

| Category | Resource name | Operation | Provider plugin   |
| -------- | ------------- | --------- | ----------------- |
| Storage  | todoDB        | No Change | awscloudformation |
| Function | todoFunc      | No Change | awscloudformation | ← out of amplify mgmt
| Api      | todoAPI       | No Change | awscloudformation |

For exapmle, you create a ruby file/method with the handler name you specified with the aws command earlier like so:

lambda_function.rb

require 'json'

def lambda_handler(event:, context:)
  { statusCode: 200, body: JSON.generate(event) }
end

Upload the file to cloud as follows

update.sh

#!/bin/bash

FUNCTION_NAME=todoFunc-dev

if [ $# -lt 1 ]; then
  echo "Usage: $0 code_file"
else
  zip lambda_function.zip $1 &&
  aws lambda update-function-code --function-name $FUNCTION_NAME --zip-file fileb://lambda_function.zip
fi
$ ./update.sh lambda_function.rb

Then try calling the API

$ curl -s https://<api-id>.execute-api.us-east-2.amazonaws.com/dev/tasks | jq
{
  "resource": "/tasks",
  "path": "/tasks",
  "httpMethod": "GET",
  "headers": {
    "Accept": "*/*",
    :
    :

Works!

Trouble shoot

If you get an "Internal server error", check the logs by:

aws logs tail /aws/lambda/todoFunc-dev

For my case, I once got this error.

2021-03-21T02:20:35.729000+00:00 2021/03/21/[$LATEST]79ed2ec4cb494600bcb8db3e59b95c91 Critical exception from handler
2021-03-21T02:20:35.729000+00:00 2021/03/21/[$LATEST]79ed2ec4cb494600bcb8db3e59b95c91 {
  "errorMessage": "undefined method `lambda_handler' for #<LambdaHandler:0x00000000020f0cb0>",
  "errorType": "Function<NoMethodError>",
  "stackTrace": [

  ]
}

Then fixed by doing Edit > Save in the lambda console GUI.

Screen Shot 2021-03-21 at 11.38.05.png

Now that I've gotten this far, I'm half wondering if there's any point in using Amplify, but it looks like I can now proceed with Ruby :)