SST for Serverless and Not-So-Serverless Apps

August 24, 2024


SST is a framework designed to enhance the development experience for serverless applications. The first two major versions provided high-level constructs for deploying widely known AWS services using AWS CDK, such as Lambda functions, S5 Buckets, SNS Topics, SQS Queues, and more. Combining these resources makes it possible to build a highly scalable and cost-efficient application (to a certain point). On the other hand, SST V3 took a completely different approach by creating a new deployment engine based on Pulumi and Terraform, promising more efficient deployments and multi-cloud support while addressing some common issues with AWS CDK.

SST abstracts the complexity of deploying these resources as much as possible while providing access to the underlying objects, allowing you to easily extend them using AWS CDK for V2 or Pulumi for V3. One of the most attractive features of SST is the ability to run Lambda functions locally. This technology, called Live Lambda, creates a WebSocket between AWS and your local machine so that all events triggering your functions are proxied to your local environment. Live Lambda enables a seamless development experience, similar to running any other application locally. Additionally, it’s possible to use a debugger. There’s no need to emulate or mock services; SST’s solution is to use real services during development, ensuring your production application behaves the way it did during local development.

One of the most common patterns for developing serverless REST APIs is to create an API Gateway and have a Lambda function for each endpoint. This approach is convenient for leveraging some of the features of API Gateway, like routing, caching, and access control. Here’s an example of what creating a REST API with SST V3 looks like:

async run() {
  const api = new sst.aws.ApiGatewayV2("TasksApi");
  api.route("GET /tasks", {
    handler: "index.get",
  });
  api.route("POST /tasks", {
    handler: "index.post",
  });
  api.route("DELETE /tasks/{id}", {
    handler: "index.delete",
  });
}

While this is probably the most widely known approach to developing serverless REST APIs, SST V3’s documentation showcases some alternatives that are gaining popularity: serverless tRPC and Hono APIs. In the past, if you wanted to deploy a Node.js application on AWS, you had multiple options, but none were convenient for someone wishing to deploy a new application quickly. The first option was to create an EC2 instance and manually install several OS packages, including a reverse proxy like NGINX, to serve your application. While every developer should go through this process at least once to understand how all the software components interact to serve a simple Node.js app, it’s neither a trivial nor practical approach. Updating OS packages and ensuring you have the correct configuration to provide security and reliability can be a full-time job.

This is where Docker becomes handy. The ability to configure the entire environment where your app runs in a single Dockerfile and then publish this image to a registry is a game-changer. It’s still not an easy process the first time (or the next several times), but you don’t have to worry as much about the OS configuration, and your deployments become more deterministic. To deploy your containers, you might come across ECS, one of AWS’s container orchestration services. Interestingly, you can choose between two different runtime environments: EC2 instances or AWS Fargate. Fargate is advertised as the “serverless, pay-as-you-go compute engine”, which sounds like it is just like Lambda, but the key difference is that Lambda runs when triggered by an event, while Fargate runs until the task finishes. You’d probably want your app to be available 24/7, 365 days a year, so the “pay-as-you-go” model might not apply to you. In this case, Fargate isn’t so serverless, as you’re paying for 100% uptime rather than actual usage. Still, not having to worry about the underlying infrastructure is a considerable advantage, and for long-running jobs that will eventually end, Fargate might be the way to go.

The fact that Hono and tRPC already provide handlers compatible with Lambda functions is a massive win for ensuring compatibility while significantly reducing the effort of deployment —especially if you’re using SST:

async run() {
  const hono = new sst.aws.Function("Hono", {
    url: true,
    handler: "index.handler",
  });

  return {
    api: hono.url,
  };
}

Or if you prefer to deploy it to a Cloudflare Worker:

async run() {
  const hono = new sst.cloudflare.Worker("Hono", {
    url: true,
    handler: "index.ts",
  });

  return {
    api: hono.url,
  };
}

For small to medium-sized projects, this approach might be an excellent option. However, it’s important to note that Lambda deployment packages can be at most 250 MB (unzipped). If that’s the case, splitting your app into multiple functions can be a solution. There has also been some discussion about whether bundle size might affect the cold start performance of your Lambda function. It’s worth doing more research if this is a concern. The good news is that if you’re using a framework like Hono or tRPC, you can always start with Lambda and eventually containerize your application and deploy it to ECS, where you have more vertical and horizontal scaling flexibility —though it wouldn’t be completely “serverless” as discussed earlier. This has never been easier than with SST V3, where you can even create a VPC without having to configure security groups, subnets, and NAT Gateways:

async run() {
  const vpc = new sst.aws.Vpc("MyVpc");

  const cluster = new sst.aws.Cluster("MyCluster", { vpc });

  cluster.addService("MyService", {
    public: {
      ports: [{ listen: "80/http" }],
    },
    dev: {
      command: "node --watch index.mjs",
    },
  });
}

Adding a Postgres database with Amazon Aurora Serverless V2 is just one more line of code. If you self-host some open-source projects, SST V3 is an excellent option for simplifying your deployments. Some projects like Strapi and Cal.com provide community Dockerfiles you can use or you can build your own.

Conclusion

SST has elevated the development experience of serverless applications and is evolving to provide more alternatives for building your applications and adapting them to your needs. You might start by deploying a simple REST API with API Gateway and a few Lambda functions, then move to Hono and tRPC to leverage some of the features of these frameworks, and eventually containerize your applications and deploy them to ECS with Fargate, where you can scale to the next level. Furthermore, if you need to self-host some projects, SST is a good option thanks to its ability to create VPCs and Postgres databases easily.

Thanks for reading my first blog post! :)