SST: The Most Underrated Serverless Framework You Need to Discover (part 2)

SST: The Most Underrated Serverless Framework You Need to Discover (part 2)

Tags
serverless
aws
cdk
sst
Published
Nov 13, 2021
This article is the second of a series around SST. I will try to let you discover some amazing aspects of this particular solution in the serverless world. You can find the first article here.
So you start building using serverless principles, and you discover the Serverless framework. Great ! You will discover here another option, that I consider superior in multiple area, the Serverless Stack (SST). In this second article, I will focus on some available constructs after introducing the concept. It can help you to build faster!

Introducing some concepts

What is CDK?

The Serverless Stack (SST) is based on the AWS Cloud Development Kit (CDK). This solution has been introduced by AWS a few years ago, it allow to build infrastructure as code (IaC) by using a real programming language. If you already know Terraform, it is an equivalent product.
Terraform allow you to declare cloud resources via the HashiCorp Configuration Language (HCL):
resource "aws_instance" "iac_in_action" {
  ami               = var.ami_id
  instance_type     = var.instance_type
  availability_zone = var.availability_zone

  // dynamically retrieve SSH Key Name
  key_name = aws_key_pair.iac_in_action.key_name

  // dynamically set Security Group ID (firewall)
  vpc_security_group_ids = [aws_security_group.iac_in_action.id]

  tags = {
    Name = "Terraform-managed EC2 Instance for IaC in Action"
  }
}
On the opposite CDK allow you to declare cloud resources via TypeScript, Python, Java or .Net (maybe more now?)!
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';

export class CdkStarterStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ... rest

    // ūüĎá create the EC2 Instance
    const ec2Instance = new ec2.Instance(this, 'ec2-instance', {
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
      role: webserverRole,
      securityGroup: webserverSG,
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.T2,
        ec2.InstanceSize.MICRO,
      ),
      machineImage: new ec2.AmazonLinuxImage({
        generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
      }),
      keyName: 'ec2-key-pair',
    });
  }
}
(this is a TypeScript example from here).
A few more differences exists between the solutions:
  • Terraform¬†can deploy on any supported cloud provider but¬†CDK¬†is designed to work only on AWS (some tooling exist to break this limit, but it's not the goal of this article!)
  • CDK is relying on CloudFormation template and stacks, so the¬†state¬†of your infrastructure is directly stored in CloudFormation stacks (not in a local json file or a S3 bucket, classic solutions for Terraform)
This will be enough to introduce you the next important concept: constructs!

What are constructs?

Constructs are the basic building blocks of AWS CDK apps. A construct represents a "cloud component" and encapsulates everything AWS CloudFormation needs to create the component.
You can think about a set of basic cloud resources are managed behind a construct, and it help you to handle the complexity more easily with a few low number of lines.
For example here we can see how it's possible to use the construct ApplicationLoadBalancedFargateService in a few lines of codes:
const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', {
  cluster,
  memoryLimitMiB: 1024,
  cpu: 512,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
  },
});
This single object is responsible to create the following:
  • a ECS task definition
  • a ECS service based on Fargate
  • an Application Load Balancer
And it can also generate for you a ECS Cluster if you provide a VPC. So in just 8 lines you create not less than 4 or 5 AWS resources!

Constructs Provided By SST

As said previously SST is based on CDK so it use intrinsically all CDK concepts, including Constructs.
One example provided in the first article was using the Api object, it's probably the most basic SST Construct available! Let's get a view on the most important constructs available in the framework and understand how it can help you.
notion image
(Go here to get the full list!)

Api

const api = new Api(this, "Api", {
  routes: {
    "GET  /notes": "src/list.main",
    "POST /notes": "src/create.main",
  },
})
The most simple one, to declare API HTTP endpoints very quickly. It mimics what can be done in the classic Serverless Framework. See my first article for a more in-depth comparison.

Auth

import { Auth } from "@serverless-stack/resources";

const auth = new Auth(this, "Auth", {
  cognito: true,
});
Auth will allow you to manage user authentication on your application, API... It's based on AWS services: Cognito User Pool and Cognito Identity Pool.
You can then manage the permissions:
auth.attachPermissionsForAuthUsers([
  api,
]);
Authenticated user of the auth object will be allowed to access the API endpoints declared by the api object (you need to secure your API for that, by default API is not secured and everyone can use it).

Table

The Table construct will handle for you a DynamoDB table:
import { Table, TableFieldType } from "@serverless-stack/resources";

const table = new Table(this, "Notes", {
  fields: {
    userId: TableFieldType.STRING,
    noteId: TableFieldType.STRING,
  },
  primaryIndex: { partitionKey: "noteId", sortKey: "userId" },
});
Then if you want to allow the Lambda functions inside your api object to be able to read / write in the table you have to specify that with the permissions prop:
new Api(this, "Api", {
  defaultFunctionProps: {
    permissions: [table],
  },
  routes: {
    "GET  /notes": "src/list.main",
    "POST /notes": "src/create.main",
  },
});

ReactStaticSite

ReactStaticSite is a very cleaver constructs that will allow you to include your frontend application inside the SST application!
Let's imagine you have a frontend/ folder inside the SST application with a classic ReactJs application. Add the following to your SST stack:
const site = new ReactStaticSite(this, "ReactSite", {
  path: "frontend/",
});
This 3-lines of code will do the following:
  • create for you a¬†S3 bucket¬†to host the frontend files
  • create for you a¬†CloudFront deployment¬†to manage a CDN for your frontend
  • build¬†your ReactJs application when you are deploying the application (sst deploy)
  • push to S3¬†the build version of your ReactJs application to the S3 bucket dedicated
  • invalidate¬†the CloudFront deployment (to get ride of existing cached data on the CDN)
And you can also imagine the power behind the¬†StaticSite¬†or the¬†NextjsSite¬†constructs ūüėÖ

Handle Environment Variables with ReactStaticSite

Generally you will use some REACT_APP_* variables in your ReactJs application in order to share the URL endpoint of your API, the Cognito User Pool Id, etc... A classic way to do that is to create a .env file at the root level of your ReactJs application (here for Create React App documentation)
SST is providing a very efficient way to handle that without messing with a .env file anymore! First define the variable in the ReactStaticSite construct like that:
new ReactStaticSite(this, "ReactSite", {
  path: "path/to/src",
  environment: {
    REACT_APP_API_URL: api.url,
    REACT_APP_USER_POOL_CLIENT: auth.cognitoUserPoolClient.userPoolClientId,
  },
});
Then in combination with the Live Lambda Development environment (it will be my next article!) you will be able to start your local React application by using the SST live information!
Add the following package to your ReactJs application:
npm install --save-dev @serverless-stack/static-site-env
And change the start script in package.json:
"scripts": {
  "start": "sst-env -- react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
},
More on this feature in the dedicated documentation.

Conclusion

This is the end of this second article about SST framework. I hope you understand better the power behind each¬†construct¬†provided by the framework. The team behind is very active and ship new features very often. I remember a few months ago, saying to myself: "if only some kind of static site could be embed into SST it will be just¬†magic". Then I see¬†v0.33.0¬†ūüėĪ.
Just give a look at the follow resources:
  • guide¬†that contains a lot of examples, and some comparison with Serverless Framework
In the next episode of this series, I will speak about the local development feature of this framework, you can follow me to be informed when it will be out!