While looking around for a frugal way to host a node app I came across Serverless architecture. Here follows a massive simplification of Serverless architecture from the perspective of a front-end developer. Essentially Serverless involves splitting out:
get-user
), these are then run on a cloud service such as AWS Lambda. When run on Lambda, these functions exist as stateless containers, and are called on demand by the front-end (e.g. via a GET endpoint of /user/{id}
).For data storage a cloud solution such as AWS DynamoDB or MongoDB Atlas can be used.
To test it out, let’s build a simple app that will store info about Australian states in a database and return current data from said database. Deploying Serverless apps is made infinity easier by the Serverless npm package, which we will be using. The source for this example can be found at https://github.com/jonjhiggins/serverless-test.
npm i serverless -g
serverless-admin
with AdministratorAccess
permissions (more info)serverless config credentials --provider aws --key AKIAIOSFODNN7EXAMPLE --secret wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
(more info)mkdir serverless-test && cd $_
serverless.yml
- this contains all the configuration the Serverless npm package needs to deploy to AWS.serverless.yml
:service: serverless-test
frameworkVersion: ">=1.1.0 <2.0.0"
provider:
name: aws
runtime: nodejs4.3
environment:
DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Scan
- dynamodb:PutItem
Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
functions:
create:
handler: states/create.create
events:
- http:
path: states
method: post
cors: true
list:
handler: states/list.list
events:
- http:
path: states
method: get
cors: true
resources:
Resources:
ServerlessTestTable:
Type: "AWS::DynamoDB::Table"
DeletionPolicy: Retain
Properties:
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:provider.environment.DYNAMODB_TABLE}
This configuration make look complicated, but there’s not too much too it:
provider
we’re hooking up to the DynamoDB table that will be our database, iamRoleStatements
allow specific actions on the table that we’ll reference in our Lambda functions.functions
we list out the Lambda functions, handler
references their path in the project (e.g. ‘states/create.js’ has a function “create”). In events
we create the HTTP endpoint (e.g. ‘/states’).resources
we create or reference DynamoDB table “ServerlessTestTable” which will store our data.states/create.js
and states/list.js
. These will contain the functions that create and list states respectively.states/create.js
:const AWS = require("aws-sdk"); // eslint-disable-line import/no-extraneous-dependencies
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.create = (event, context, callback) => {
const uuid = Math.floor(Math.random() * 100000000).toString();
const data = JSON.parse(event.body);
if (
typeof data.state !== "string" ||
typeof data.slogan !== "string" ||
typeof data.capital !== "string"
) {
console.error("Validation Failed");
callback(new Error("Couldn't create a new Australian state."));
return;
}
const params = {
TableName: process.env.DYNAMODB_TABLE,
Item: {
id: uuid,
state: data.state,
slogan: data.slogan,
capital: data.capital,
},
};
// write the state to the database
dynamoDb.put(params, (error) => {
// handle potential errors
if (error) {
console.error(error);
callback(new Error("Couldn't create a new Australian state."));
return;
}
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(params.Item),
};
callback(null, response);
});
};
states/list.js
:const AWS = require("aws-sdk"); // eslint-disable-line import/no-extraneous-dependencies
const dynamoDb = new AWS.DynamoDB.DocumentClient();
const params = {
TableName: process.env.DYNAMODB_TABLE,
};
module.exports.list = (event, context, callback) => {
// fetch all states from the database
dynamoDb.scan(params, (error, result) => {
// handle potential errors
if (error) {
console.error(error);
callback(new Error("Couldn't fetch the Australian states."));
return;
}
// create a response
const response = {
statusCode: 200,
body: JSON.stringify(result.Items),
};
callback(null, response);
});
};
serverless deploy
, make a note of the endpoint URLs at the endcurl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/states --data '{ "state": "South Australia", "slogan": "The Wine State", "capital": "Adelaide" }'
curl -X GET https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/states
That simple example was adapted an the example project on Serverless examples, there’s loads more examples in the parent repo. In particular, the offline examples are useful for running Serverless locally for debugging. There’s no front-end for this example, but that could be created and hosted on S3.