Scratch your own itch

While working as a freelancer for several start-up companies, I always seemed to encounter the same issue. At a start-up, you usually build custom software and don’t bother to use a CMS. This works fine, until a marketing person shows up at your desk, demanding to change some texts on the website, or the dashboard, or just a in a random button somewhere — and always at that moment when you were close to a solution for something really complex.

With growth, this tends to happen more and more, and a cumbersome manual procedure will then ensue. And manual labour is prone to errors, too. The following actually happened to me once. After creating an issue in our issue tracker, planning the change for the next sprint, changing the code, deploying the change to staging where everything can be tested and finally, about two weeks later, launching the requested text change on production — I found out that I created a new typo in the text. Bummer.

Of course, as a solution, you could decide to add a CMS in the mix. Needless to say, this is a lot of work. Worse is, that it limits the creativity to freely design whatever you want, since most CMS are technically “website builders” rather than just text management mechanisms. This effectively renders them useless for anything else but a website that looks exactly like the one everybody else has.

However, by far the largest issue is that a typical CMS tends to suck at localisation. With growth comes the need for internationalisation and other languages, right? So, at some point, the content on the website needs to be translated.

So, two issues needed a solution: text management and localisation. Both issues are fixable, but that could take weeks to develop. And that’s precious time that a start-up just cannot afford. Since we felt this pain ourselves, we decided to solve it with a new service. So, we came up with the idea of Instant, about 9 months ago. It was going to be a drop-in service for adding content management and localisation to any website, without a lot of required programming and super simple to use for non-technical editors.

I talked to two of my clients and both agreed to try it as launching customers. For a friend and myself, this was enough to start working on it in the weekends and evenings. Since this was a side project (one of many), we didn’t want to spend lots of money on it. Keeping it up and running for the first two clients shouldn’t be expensive, since we didn’t know if it would make sense to anybody else.

So, how could we keep costs to an acceptable minimum? Enter serverless architectures.

Serverless

At about the same time that we started thinking about our new thing, Amazon launched a couple of new services — called AWS Lambda and API Gateway.

AWS Lambda lets you run code without provisioning or managing servers. You pay only for the compute time you consume — there is no charge when your code is not running.

Ok, that sounds awesome! So, if nobody uses the service, you literally pay nothing at all and the costs of running the service will scale up with the number of users. Sweet.

The term “serverless” pops to mind: Lambda functions may run on servers, but we don’t have to know anything about those and — even better — we don’t have to maintain anything or worry about keeping it secured. As far as we are concerned, Lambda could run on bike-powered electricity generators operated by elves. We really don’t care.

Anyway, you can connect Lambda functions to specific events in the AWS environment — for example if someone uploads a file to an S3 bucket. In our case, we use API Gateway to expose the functions through a nice RESTful API.

Example Lambda function

If you would like to do this yourself, make sure to add any NPM dependency as a bundled dependency in your NPM configuration. We created this super-simple grunt script to deploy the Lambda function to your production environment. It uses the grunt-aws-lambda NPM package to create a bundled ZIP file of your function and dependencies and upload it to AWS.

Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale.

The API gateway is pretty straightforward, you simply define your API in a simple interface and add the options you need. You can define resources (endpoints) and methods (GET, POST, etc). Then you just connect a method to a Lambda function (or some other AWS endpoint like a S3 bucket) and modify the request/response headers if necessary. Another thing you don’t have to worry about is cross origin scripting, the API gateway supports CORS out of the box. The API gateway has IAM integration support to make sure all the users of your API are properly authenticated. Lastly I’d like to mention you can set throughput limits and burst limits for each of your API methods.

Storing data

We now have a way to run code in the cloud exposed through a restful API, but if the functions run on different servers we don’t know anything about, where do you put the data? Well, for our service, we use two types of storage: S3 and DynamoDB.

Amazon DynamoDB is a fast and flexible NoSQL database service for all applications that need consistent, single-digit millisecond latency at any scale. It is a fully managed cloud database and supports both document and key-value store models.

In our case, a draft of an editable text is saved automatically if you change a text on your website using our txt.js script. This draft is stored with some meta information in DynamoDB.

Since the DynamoDB syntax itself is pretty verbose using the standard AWS NPM package, we rather use the dynamodb-doc package. This package makes it a bit easier to store and fetch records from the DynamoDB database.

The biggest pitfall of using DynamoDB is that it is not a relational database. You really have to think different about your data and program differently, otherwise it won’t work — and you will be confused and frustrated. If you haven’t used a NoSQL database before I suggest doing some thorough research before you start coding.

A lambda function that updates an Item in DynamoDB

A request that goes through API gateway → Lambda → DynamoDB and back takes about 300~600ms. This is fine for editing and loading a draft, but it is not fast enough if we want to serve content directly to our customers websites. To achieve much faster speeds we push the latest version of a draft to an S3 bucket and serve this data through CloudFront.

To store a published draft as a static file in S3 and serving it through a high speed world-wide CDN network improves the speed dramatically. We now typically serve the content to the clients in just 30~50ms!

Authentication

When someone calls the API to update a draft we have to make sure only his or her own content can be accessed. So, we needed some sort of authentication mechanism. Of course, we don’t want to reinvent the wheel, so we would rather want to use something that is thoroughly tested and proven secure — like OAuth or OpenID. But let’s check AWS first.

Amazon Cognito lets you easily add user sign-up and sign-in to your mobile and web apps. With Amazon Cognito, you can also authenticate users through social identity providers such as Facebook, Twitter, or Amazon, or by using your own identity solution.

Perfect! By using Cognito we can let users sign up and log in using their Google account, without ever sharing their password with us. When a user authenticates using Cognito, they will get a temporary IAM role assigned. This role can be used to call our API gateway. Without it, you simply cannot call the API Gateway methods.

Connecting the Google API to AWS Cognito

When calling a method on the API, you can use the id_token you received from Google to securely check in your Lambda function — if the user is who he says he is.

Double-check the Google certificate in your Lambda function

As you can see we check the local disk /tmp to see if the oauth2 certificate is there, so we don’t have to download it on every request. This a feature of AWS Lambda. It tries to group your function calls on the same server, so subsequent requests can use the same disk. This is not guaranteed — so you should program like it will run on a different server at every function call.

Hosting a website

Now we have a fully functioning service with a nice RESTful API. To tell the world it exists we need a website. Now that we are all into going serverless, let’s build a website without a server, too.

How do you host a website without a server? That’s easy. Just use S3. This AWS storage service has a nice feature that allows you to host a static website and serve it directly from S3. For performance gain, you should always tunnel your visitors through CloudFront. There are several options if you are to build a static website.

Upload a simple, hand crafted HTML page with some javascript

Use a single page application framework like ReactJs

Apply a static site generator like Jekyll

We chose to build our static site in React (here are 6 reasons to use React).

Normally, you serve React from a server. It is not static by default. To make a static build, you can use Webpack and a simple build script.

Generating a static site from script

Webpack compiles your project into a main.js and main.css file. In your index.html, use a placeholder SCRIPT_URL which will be replaced by this generated main.js file with a timestamp. You need the timestamp, because every built version that you upload to S3 should have a separate version to improve cache hits — make sure to pass through query strings in your CloudFront configuration. Simply upload your files to S3 and your website is ready to go.

One more thing, though. Typically, a static React app uses HTML5 pushState and React Router for URL handling. That’s all fine in the browser. However, if you access any URL other than the root directly (following a deeplink), S3 tends to get lost — because on the file system, this URL does not exist. We found a solution, though.

Configure a static website in S3, with the root pointing to the main React index.html file. Do not add any S3 redirect rules — not even the custom error page. Create a Cloudfront distribution pointing to the S3 bucket. Create a custom error response in Cloudfront, that points to the main index.html file as well. Make sure to enforce a fixed 200 response.

The result is that all URL paths (except for the root) lead to a 404 response in S3, which then triggers the cached custom error response of Cloudfront. This final response is just your single-page app again. Now, in the browser, you can handle all routing based on the current path.

There is only one disadvantage. You can’t return an actual 404 HTTP response code in any case. However, in return, you will get an ultra-cheap, ultra-scalable, single-page app!

And now for the commercial break: to make your static website a little less static or even multi-language, you can simply implement our simple Instant script. With just a couple of lines of coding, you can make every text on your website instantly editable by any person who is associated with your account. We will host and serve the texts for you. It is multi-lingual by default, just switch the locale in javascript and start translating your strings. This works with any website, not only static sites.

Putting it all together

Here you go, we now have an architecture that requires zero maintenance and is ultra scalable. To finish it off, here is a nice diagram of our serverless architecture as described above. I omitted the Google authentication, because it is just client-side javascript and not really connected to the service. If you have any questions just reply to this blog and I’ll be happy to help you out!