I finished a new project recently, a SaaS product built from the ground up. It was to have a web portal, an API, user authentication, a database and transational emails.
I wasn't sure if the platform would even bring in paying users, at least not for several months or even years, so monthly running costs were a key concern. I decided to architect the entire platform with one simple question in mind:
What is the cheapest way to run a SaaS platform in 2020?
Before I dive into the answer - here is an overview of the SaaS platform we are going to architect
- A web portal with the ability to create an account and navigate around some dashboards
- A Rest API capable of serving the data and accepting updates from the front end
- A database for securely storing all the data entered into the platform by users
- Transactional emails to send updates to users
- A marketing website and blog to go alongside the portal for promotion and general internet presence
A Traditional Split
Traditionally, to break down the requirements above lines are often drawn around expertise. You might have front end developers who will build the front end UI, back end devs and domain experts who will model the database and back end, marketing folks who will look after the emails and ancilliary content pages. This creates an architecture for the entire system split into what I call "bubbles of expertise"
While this may be a great way to split the platform out based on expertise, it doesn't neccessarily lend to a cheap design. Your back end developers will most likely lean towards excellent but expensive tooling such as a constantly running build server, many always-up web services, a fully featured relational database (again, always running). The marketing folk will be asking for subscriptions to Mailchimp and expensive WordPress plugins and tools to build their marketing website. In order to engineer for cost we need to throw this view away and build up a new picture with monthly budget in the forefront of our minds.
Instead of splitting our architecture out into bubbles of expertise like this, we should try to redefine what the bahaviours are and aim to minimise the cost for each one seperately
Pay As You Go
In theory, the most cost effective way to run a SaaS as it grows from zero to light/medium use should be a pay-as-you-go-model. Until launch of the beta there will be nearly zero use, then a few beta users will begin appearing, then a few real users, then a few more real users and usage will ramp up. This "usage" will be everything from website visits, database operations, account creations. These things should all scale approximately in line with each other as the product begins to get adopted.
So in the quest for cost-effectiveness we need to get the running costs to mirror this curve - which an entirely pay-as-you-go model should achieve. Pay-as-you-go may not be the cheapest solution at every point along the above curve, but when looking at the adoption as a whole it will probably offer the best value for money overall, and crucially - it will be the cheapest at the beginning of the curve. The beginning is where the project is most at risk of being terminated, so if we can make the running costs in this initial section zero then we will minimise the loss if the project is terminated early.
A New Split
Here is the same architecture but arranged differently. By moving Authentication out of any "back end" and into a auth-as-a-service solution we can save both money and development time. Hosting the entire thing on the tooling of one cloud provider can also be a huge money saver if you can swallow the vendor lock-in. Below I will break down these areas and which provider I found to be the most cost-effective.
Requirements: Respond to HTTPs requests with HTML data as fast and cheaply as possible
First and foremost any web application needs to serve HTML. It is the bedrock of any website, web app, and, increasingly, native mobile/desktop app too. But an assumption a lot of people make immediately is that your HTML needs to be dynamically generated on the fly. Poke around the codebase of nearly any “enterprise” application built after 2005 and you’ll see web servers responding to HTTP requests, reading parameters from headers, cookies and the URL and rendering some HTML based on those parameters. But does it have to be that way? Certainly not, if we are engineering this to reduce cost we need to throw away that crutch and embrace the JAM stack.
Your web app will be static HTML files served from a cache. Any private or otherwise dynamic data will be loaded in afterwards as JSON from an API. The only HTML that your site visitors or users will ever see will be static, prerendered, multi-purpose HTML files.
Once we make this leap it’s pretty easy to decide the most cost-effective way to host our HTML. GitHub pages and S3 buckets both spring to mind and would make excellent choices, but one service overshadows them all - the darling of the modern webmaster - Netlify.
The great thing about choosing Netlify to serve all the static HTML, is you can now kill two birds with one stone and chalk Marketing Site off the list as well. Using Netlify's best friend: static site builder Gatsby, we can create all the pages we need for a marketing site, product landing page, tutorials, guides and whatever other content we want to publish to go alongside our app.
Requirements: Secure authentication, user account management, federated identities
Solution: AWS Cognito
When it comes to authentication you really need to pick a third party authentication provider, don’t go building this yourself. Not only is it more secure to use a trusted third party to provide authentication to your app, it is also often cheaper and that is what we are concerned with today. There are many auth-as-a-service choices out there to meet your needs and there isn’t a huge amount to choose between them, so I’d recommend using whatever your cloud service provides as it will integrate most easily with the rest of your stack. For this article we are going with AWS because when all is said and done, it will be the cheapest for what we are building here. The authentication provider in AWS is called Cognito. It is slightly clunky and encourages you towards the not-so-great AWS Amplify package for the front end - but it allows us to use AWS for the rest of the back end so it's a trade off worth making. Cognito gives you 50,000 monthly active users on the free tier alone. Plenty.
Requirements: Provide a Restful JSON API for the front end
Solution: API Gateway + AWS Lambda + Serverless Framework
Earlier I mentioned the JAM stack - well this is the “A” in JAM. An API for hydrating your static HTML with user-specific data, secured to the logged in user using the authentication token provided by your auth provider.
The reason I went with AWS over Azure or GCP is because of this aspect of the application. API Gateway + AWS Lambda work very well together and between them provide a perfectly scalable pay-as-you-go Rest API. This might seem complicated, and compared to a traditional web service it is, but the cost benefit of having a pay-as-you-go API can be enormous. Imagine extremely low latency reads and writes under heavy load that seamlessly scales down again to zero when nobody is making requests. The complication factor can be alleviated with Serverless Framework which, if you are going down this road, I highly recommend trying out.
Requirements: Pay as you go pricing model
I can hear you cry already. DynamoDB? That famously expensive database-as-a-service offering from AWS that has been known to cause spiralling costs and gets so much bad press?
Yes, and for two simple reasons - DynamoDB is managed by AWS and it has pay-as-you-go-pricing. There are better databases for sure. There are cheaper databases at scale, and there are certainly easier databases to code against - but the pricing model of DynamoDB, and the ease at which it integrates with Cognito identities makes it a pretty solid choice for small to medium sized throughput.
You do have to take a couple of precautions when coding against DynamoDB in order to keep the costs low. Batch your writes where possible. Structure your data efficiently (adjacency list pattern works great on DynamoDB) and only read what you have to. Cache in the front end as much as you feel comfortable with so you aren’t making lots of expensive GET requests to your database. If you need to do a lot of reading it can actually pay off to replicate your data somewhere cheaper such as an S3 bucket. You can write to dynamoDB from a lambda, trigger a second lambda to replicate a “read model” to S3, then read from S3.
Requirements: Send emails asynchronously in response to events, run scheduled tasks
Solution: AWS Lambda + Amazon SES
This one is easy. Running arbitrary back end code in response to an event, or on a schedule, is the bread and butter of cloud functions. Since we are in AWS already we can simple add some more AWS Lambda functions to our Serverless Framework stack and hook them up to DynamoDB events, CloudWatch triggers, or special “admin-only” API endpoints secured with an API key (all possible in Serverless Framework).
Emails are extremely cheap on Amazon SES too. Sure, you don't get anywhere near as much functionality as a service like Mailchimp for things like running campaigns, but with all the time you saved not coding authentication endpoints into the API you can build this yourself. Think of the savings!
Requirements: Push and forget deployments from GitHub
Solution: Netlify for static HTML, AWS Code Pipeline for API
We talked before about Netlify and yes, they have a brilliant build system built in. The service supports branch deployments and basic A/B testing. But what about the API?
Well since we are in AWS world let's take a look at what AWS offers. Code Pipeline is comparatively basic, especially when compared to the likes of CircleCI, Travis or TeamCity - but for the grand price of zero for our one application it is a pretty clear winner in this case. What you miss in features you will make up for with how easily everything integrates with your AWS stack.
So there you have it. The cheapest stack I could engineer for a small, self-funded SaaS platform. Is it the most beautiful? No. Does it tie me in horribly with AWS? Yes. But is it cheap? Well so far I've been spending a grand total of $0.50 US Dollars per month, and that is just for Route53 hosting. The free tier on AWS doesn't stretch forever, but it will cover you until you get some money coming in from paying users.
Of course, if you prefer to use Azure or GCP in 2020 these other cloud providers have almost all of the products listed above at very comparative prices. So you could do this stack on GCP or Azure if you wanted to and still make the savings. I'm not recommending AWS specifically, but after costing each it really came down to how extensive the product offerings were, not how nice they were.