Serverless Web Apps by Example
Disappearing Servers
There's a lot of talk about serverless architecture and serverless web apps. But what exactly does a serverless web application look like?
I've built some of my own serverless apps and have been carefully surveying the serverless landscape. As I see it, there are three classes of serverless web architectures being used today. I describe them here, listed from simple to complex.
Type 1: File hosting service architecture
Provider examples:
It doesn't get much more simple than uploading some pages to a file hosting service. The foundational technology for this approach has been around for decades! Each path on the app's domain is represented by a file. And that's it.
But wait a minute... You can't build an app with static pages! If you could, we wouldn't have made CGI! And PHP! And Rails! And the 90s Tech Bubble! And UX consultants! And "I'd like to add you to my professional network on LinkedIn"!
Well. Maybe that's not completely correct. But we have certainly come much farther than static files in the last 20 years. Why would we want to go back?
1st Reason: JavaScript
You can build dynamic, engaging client applications with JavaScript. You know this. I know this.
However, for years, I overlooked that this is often all you need.
With just an index.html page, a JavaScript app bundle, and a stylesheet, we have everything we need for a basic web app.
Our index.html
file might look like this:
<!doctype html>
<html lang="en">
<head>
<meta charset='utf-8'/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A decent web app</title>
<link href="/css/app.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="/js/app.js"></script>
</head>
<body></body>
</html>
Sure, you might throw in an additional stylesheet or a web font. But the point is, everything is a static file or a link to a file on a CDN.
All the complexity is in that app.js
file, which can inject the app content into the body tag.
To get started, you could just start writing JavaScript in app.js
. Break all the rules! No file-based modules! No transpiling! It's ok - just get something on the page.
Of course, once your app starts to get more complicated, you probably will want to add a build step. You can just use browserify or webpack on the command line or as a npm script. If you have never tried a Javascript bundler before, you'll find that these tools are mature, well-documented, and (relatively) easy to use.
2nd Reason: Great APIs
SASS products of all stripes seem to have an API these days, many of them well-documented. The expectation of an API is a new development; one that enables all kinds of fun and interesting web applications. Many kinds of web apps need only talk to an API.
This first kind of serverless architecture has a big limitation though: web apps based on static file hosting services can't maintain state across sesssions. Your users cannot save configuration data specific to your app. There's no database outside of the browser to save to. But you may not need one! If you are visualizing data from an API or building a custom UI, you can simply read from and write to the API.
When deciding if an API can be used for your serverless web app, look for APIs that support in-browser authentication. For OAuth 2.0, this means using an Implicit Grant, not an Authorization Code Grant (which requires a server). Lots of APIs support Implicit Grants, or something like it. For instance, Google's APIs.
A file hosting service example
At Adept Marketing, we use Asana to plan our projects and allocate resources. However, we needed a way to visualize our project timelines and the resources assigned to them. Asana provided no such visualization, but they do provide a great API! They even use OAuth2 Implicit Grant for auth.
So, we built a task visualization app with Backbone.js and D3.js.
The app is simple. After logging in using OAuth, all relevant tasks are fetched from the API.
Then, we visualize the tasks and provide some navigation.
When we are ready to release a new version, we run our build step (we're using gulp) and upload the files to S3, where the site is hosted. The gulp deploy task that copies everything under the build/
directory to S3 is shown below for your amusement. But you don't need all that. Just use the AWS CLI to copy the directory.
gulp.task('deploy', ['assets', 'bundle'], function (cb) {
var findit = require('findit')('build'),
fs = require('fs'),
path = require('path'),
files = [],
P = require('bluebird'),
readFile = P.promisify(fs.readFile),
AWS = require('aws-sdk'),
config = require('./config.json'),
s3 = P.promisifyAll(new AWS.S3({
accessKeyId: config.aws.accessKeyId,
secretAccessKey: config.aws.secretAccessKey
//logger: process.stdout
}));
findit.on('file', function (f) {
files.push(f.substr(6));
});
findit.on('end', function () {
P.all(_.map(files, function (fstr) {
var contentType;
switch (path.extname(fstr)) {
case '.html':
contentType = 'text/html';
break;
case '.css':
contentType = 'text/css';
break;
case '.js':
contentType = 'application/javascript';
break;
default:
contentType = 'application/octet-stream';
}
return readFile('build/' + fstr, {encoding: 'utf8'}).then(function (data) {
return s3.putObjectAsync({
Bucket: '<BUCKET>',
Key: fstr,
Body: data,
ContentType: contentType
});
});
})).then(function () {
cb();
});
});
});
This web app was a huge win for our resource manager, and it is nothing more than one HTML page, a JS bundle, and some CSS.
What else could you build with a file hosting service and an API?
A quick Google search for APIs supporting OAuth2 Implicit Grant turns up a few interesting ones. Spotify. Getty Images. Google. And many others.
Type 2: Backend as a Service (BAAS) Architecture
Provider examples:
But usually, your app really does need to save state. Your users login, make changes, and expect to see those changes later in some other browser. File hosting services can't provide this functionality alone. You need a backend.
Mobile app developers have been using Backend as a Service providers like Firebase for years now. But they work really well for web apps too.
Check out my post on building a serverless app using Firebase. I built a word magnet game that uses GitHub for auth and stores all the phrases and user info on Firebase. It was a great experience.
If your app has a straightforward data model, why not skip the servers and the databases and just use a BAAS provider like Firebase?
What else could you build as a BAAS serverless app?
Out of the box, Firebase supports Google, Facebook, Twitter, and GitHub for auth. That means that you could create a rich, interactive serverless app that makes use of any of the APIs exposed by those companies. You would use their auth token to both log the user in to Firebase and also to make calls against their API.
Type 3: Functions as a Service (FAAS) Architecture
Provider examples:
The final type of serverless architecture considered here uses Functions as a Service to build out a custom backend. FAAS is a relatively new player in the cloud ecosystem - Amazon introduced AWS Lambda in 2014. With FAAS, individual functions can be uploaded to the service or edited directly in the service UI. Those functions are configured to respond to events triggered by other cloud services. The FAAS platform presumably spins up short-lived containers that run the code, then removes the container when it is no longer needed.
At first glance, FAAS seems like a great fit for certain kinds of web apps. Instead of using BAAS to store state, we could design a series of microservices that run on a FAAS platform and provide much more control over our backend data. In conjunction with a cloud service database like DynamoDB, our FAAS functions could be a formidable backend with effortless horizontal scaling.
However, in practice, this doesn't really work very well. Others have written about this, but here are some of the problems I encountered while trying to create a web backend based on AWS Lambda:
Bad developer experience
There is no official way to run AWS Lambda locally while you develop. There are community attempts to mirror the Lambda/API Gateway environment locally. However, configuration of these mock environments is cumbersome.
AWS API Gateway is difficult to configure and maintain
note: AWS has made some updates to API Gateway and Lambda since I wrote this post. See this announcement for details. It is now easier to pass request information from the API Gateway endpoint to a Lambda function.
Lambda functions are not designed to respond to HTTP requests. Some translation layer between the request and the Lambda event is required. You don't realize how essential request headers are until you no longer have access to them!
So, you tie Lambda functions to AWS API Gateway endpoints. These endpoints themselves have lots of options that describe what release they are associated with and what security model they should use. The endpoints can also inject information into the Lambda event that will be received by the Lambda function. This injection step is configured with a DSL. So yes, it is possible to get all the information you need in the Lambda function. But no, it is not simple.
AWS Lambda functions don't integrate well with other AWS services
Lambda functions appear to be a perfect candidate for a share-nothing, horizontally scaling backend. Of course, we'll need somewhere to read and write state from time to time. How about redis? After all, AWS has a cloud service for that - AWS ElastiCache. I expected to click a few buttons and have a redis endpoint to use in my Lambda functions.
But no. Using AWS ElastiCache requires deploying your Lambda functions in a non-default way. Specifically, you must deploy them into a specially-configured Virtual Private Cloud. Prepare to take a deep dive into VPC configuration. You'll also need to dust off your networking and routing skills. If you want your Lambda functions to continue to be able to reach network destinations on the Internet, your VPC will need to have at least two subnets: one for the Lambda function and one with a VPC NAT Gateway. Oh and that NAT Gateway will cost you. Unlike Lambda, there is no free tier for those.
I trust you're getting the point. FAAS functions are probably really good for the asynchronous tasks that they were originally marketed for. But, so far, they are rather inconvenient for web backends.
note: Google Functions seems to have a better story for web-triggered events, but, as far as I can tell, the other problems exist there too.
Conclusion
Next time you hear about serverless architecture for web apps, ask yourself what kind of serverless design you are dealing with.
For some projects, file hosting is all you need! If the only thing your app needs to do is interact with a good API, you may be able to build an app that is extremely easy to maintain using a file hosting service like S3.
And if your app doesn't need to talk to an API, but just needs to read and save some state, OR if your app happens to be dealing with Google, Facebook, GitHub, or Twitter, why not use Firebase? Building an app with no servers is a real joy.
Finally, take my advice and don't use FAAS yet for your custom backend endpoints. By all means, use FAAS for support tasks, but let's let the major vendors update their offerings before we try to build our own APIs with them.
note: there has been some progress here. See this announcement.