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 -gserverless-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/statesThat 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.