Blogging the Weird Way

How to build a serverless blog using Golang and AWS Lambda

"It is fun to have fun, but you have to know how." -- The Cat in the Hat

You may have heard the recent announcement that Go is now a first class citizen of AWS Lambda. This means we can deploy pure Go functions to the cloud without engaging in any funny business. Of course it is funny, this serverless world. There's a tantalizing weirdness to it.

I want to write more Go. I also want to write more. I don't want to manage more servers or wonder How many instances do I have running? Although the answer is always known to us: One more than you thought, sucker!

Let's simplify. Let's also get as weird as we possibly can.

Step 1: Seek Help

The Serverless Framework is great. It's a suite of tools that will set everything up for you in Amazon-land. We are not here to become Amazon Certified Architects; we are here to make questionable decisions about how to set up a blog. We will use it.

If "suite" makes you shilly-shally, do not lose heart. It only takes two Serverless commands to build and deploy the AWS Lambda equivalent of "Hello, World!"

There are, of course, prerequisites:

1) You've installed Go: https://golang.org.
2) You have already set up an AWS account and an IAM user with admin power.
3) You have retrieved the AWS Access Key ID and Secret for that user and either set your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or run the following command to tell Serverless which credentials to use:

serverless config credentials --provider aws --key ACCESS_KEY --secret SECRET

Head on over to this Serverless.com blog entry to finish getting set up. You're already halfway there. By the end you'll have a nice little Lambda endpoint built on AWS API Gateway that takes a POST request and returns JSON. We will build from there.

Step 2: GET HTML

We're going to be returning web pages from our Lambda functions. To move this party to a web browser, we first need to switch verbs.

Edit your serverless.yml and make your POST a GET:

# serverless.yml

functions:
  echo:
    handler: bin/main
    events:
      - http:
          path: echo
          method: post    --------> method: get
	

Now deploy:

$ sls deploy
You'll see your endpoint listed in the output. Load that in a web browser and... nothing. We need to actually return some content.

Step 3: First Post!

Let's modify our handler:

# main.go

func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
  return events.APIGatewayProxyResponse{
    Headers:    map[string]string{"content-type": "text/html"},
    Body:       "<html><body><h1>First Post!</h1></body></html>",
    StatusCode: 200,
  }, nil
}

Changing the content-type header is sufficient to get our HTML rendering in browsers. You can, of course, add any other headers you require. Always mind your headers. Once you've got them ship-shape, drop them into the Headers map.

Redeploy and Bob's your uncle.

Step 4: Bob Is Not Your Uncle

Go has a template engine. We can use it.

In the typical scenario, HTML templates are parsed from files on disk, executed (i.e. template data fields are populated), and written to an io.Writer -- often an http.ResponseWriter. We, however, will not be dealing in such prosaic things as "files" or "databases". We are going for parsimony and weirdness. Both of those goals are served well by storing our HTML template in the Go code as a string that is parsed and written to a bytes.Buffer. The Buffer object implements io.Writer, so this works fine, and it has a handy String() method that allows us to write our executed template to the Body field of the APIGatewayProxyResponse object.

Let's write a function that takes a string representing our template and returns a bytes.Buffer suitable for use when we're constructing our API Gateway response. It might look like this (Narrator: It does look like this):

// BuildPage parses an HTML template string, executes the resulting template,
// and returns the result as a bytes.Buffer. You can call .String() on the
// result and send it on in an HTTP response (or wherever).
func BuildPage(htmlTemplate string) *bytes.Buffer {
  var bodyBuffer bytes.Buffer

  // Parse
  t, err := template.New("").Parse(htmlTemplate)
  if err != nil {
    bodyBuffer.Write([]byte("Indications of what humans would call... a wild party."))
    return &bodyBuffer
  }

  // Execute!
  err = t.Execute(&bodyBuffer, nil)
  if err != nil {
    bodyBuffer.Write([]byte("It is possible to commit no mistakes and still lose. That is not a weakness. That is life."))
    return &bodyBuffer
  }
  return &bodyBuffer
}

Great! Let's add a template string to our Lambda function:

# main.go

var HtmlTemplate = '
<html>
  <body>
    <h1>First Post!</h1>
  </body>
</html>
'

And update our Handler to store the page content in the Body of the response:

func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
  return events.APIGatewayProxyResponse{
    Headers:    map[string]string{"content-type": "text/html"},
    Body:       BuildPage(HtmlTemplate).String(),
    StatusCode: 200,
  }, nil
}

It's a beginning, of sorts. We have a simple handler that calls a testable function to build our pages. And you've probably guessed at the mild twist in all of this: We don't need to build a blog, it can be a normal web site. In fact, you probably shouldn't build a blog. And you probably shouldn't use Lambda to build a simple web site. It is upsetting to people. But I did. And so can you.

anh