Dealing with a database
S3 is great for static files, but what if you want a little more?
For dynamic data, we'll use a database. DynamoDB integrates nicely with the Serverless ecosystem and doesn't require a lot of setup.
Choosing a database for your project in the real world is a matter of balancing various factors.
Create a DynamoDB table
DynamoDB stores data in tables. Each works as its own database and you can have as many as you want.
Exercise
Move into exercise-6 in the exercises repository.
Create a new YAML file to specify a new DynamoDB table.
# dynamodb.ymlResources:ItemsTable:Type: "AWS::DynamoDB::Table"Properties:AttributeDefinitions:- AttributeName: itemIdAttributeType: SKeySchema:- AttributeName: itemIdKeyType: HASHProvisionedThroughput:ReadCapacityUnits: 1WriteCapacityUnits: 1TableName: ${self:provider.environment.ITEMS_TABLE}
This creates a DynamoDB table with an identifier.
Add this table to your serverless config. Don't forget to install serverless-resources-env
# serverless.ymlresources:- ${file(dynamodb.yml)}plugin:- serverless-resources-env
You can now access your DynamoDB's name as process.env.CF_ItemsTable
You'll need to grant access and specify the name. I recommend starting with liberal permissions.
# serverless.ymlprovider:environment:ITEMS_TABLE: ${self:service}-items-${self:provider.stage}iamRoleStatements:- Effect: AllowAction:- dynamodb:Query- dynamodb:Scan- dynamodb:GetItem- dynamodb:PutItem- dynamodb:UpdateItem- dynamodb:DeleteItemResource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.ITEMS_TABLE}"
Install the simple-dynamodb
package. It's a thin wrapper I built to help with common DynamoDB operations.
Solution
Write to your table
Before you can read from a table, you have to store something. We're going to treat every write as an upsert for simplicity.
Create item on first call, update on subsequent calls.
Exercise
Create a new lambda function handling a POST request. Accept a JSON payload and an itemId
path param.
Save the JSON object in your table with code like this:
import * as db from "./dynamodb"const updatedAt = new Date().toISOString()// db.buildExpression and db.buildAttributes are helper functions to create DynamoDB queriesconst item = await db.updateItem({TableName: process.env.ITEMS_TABLE!,Key: { itemId },UpdateExpression: `SET ${db.buildExpression(body)}, updatedAt = :updatedAt`,ExpressionAttributeValues: {...db.buildAttributes(body),":updatedAt": updatedAt,},ReturnValues: "ALL_NEW",})
You can access saved values via item.Attributes
. I recommend returning them.
Try your function
Leave payload empty for GET requests, valid JSON for POST.
Solution
Read from your table
Reading from your table is easier. My wrapper provides a helpful db.getItem
method.
Exercise
Create a new function that handles a GET request. Use a path param for the ID.
Use code like this to read from your table.
// read itemId from pathParametersconst item = await db.getItem({TableName: process.env.ITEMS_TABLE!,Key: { itemId },})
Return item.Item
if found. 404 status code if not.
Try your function
Leave payload empty for GET requests, valid JSON for POST.