What local dev can feel like— Photo by David Kovalenko

Welcome! I’ve recently been re-evaluating my AWS Lambda and Go local development. I’ve learnt a lot and tried a few different approaches, so I wanted to share it with the wider community. This story really starts with wanting to run my Go Lambda functions locally alongside integrations like AWS Cognito.

My local development requirements were:

It needs hot reloading aka it needs to be fast . Ideally so fast you would think it’s regular local Go development

aka . Ideally so fast you would think it’s regular local Go development JWT Tokens from cognito need to work. This is noted as the application itself does not parse the JWT tokens, but APIGateway attaches a APIGatewayProxyRequest object to the context of the request. This APIGatewayProxyRequest is filled with important things like the claims map, which tells us which user is requesting resources etc.

This doesn’t seem overly difficult from the outset, but it’s deceiving.

Challenge #1

First and most importantly we need to reliably run our Lambda code locally. The main options I tried and considered were:

Serverless framework and the offline plugin

Serverless is a very reliable deployment tool that I use in my project to deploy my Lambda code to AWS. I’ve been very happy with the process of deployment. But the support for local development comes from a plugin serverless offline .

Speed 💩💩: The plugin itself is great and works as expected but man is it SLOW with a capital “S”. My simple web server would take anywhere from 20–50 seconds to fulfil a request(on a decent 2017 3.1ghz 16gb macbook pro). It seems every request needs to rebuild a docker container. Unfortunately, I could not find a way to mount volumes, if there is a way please let me know!

Serverless Framework, one long request

Environment 🔥: It handles the cognito tokens as expected 👍 and acts like the real Lambda environment.

Extra work 🔥: I already use it to deploy my lambdas, so there’s no extra yaml files to setup.

AWS Sam

The official AWS solution. I had visited this about a year ago when I was first starting my project and wanted to see if it ticked all my boxes. I wanted to use the official solution but it didn’t have a nice integration with cognito , and it still looks like it’s overly complex and still has no support for cognito locally 👎.

Speed 👍: SAM is fast. A request typically takes about 3 seconds. It mounts volumes and thus hot reloads the go binaries. So we just need to make sure building our binaries is just as fast.

Environment 👍: Being the official solution I would have expected it to handle the cognito tokens. But alas it doesn’t, so it’s a bit shit.

Extra work 💩: First we need an extra yaml file to declare routes. But the real headache starts when you want to parse that cognito token. This means a bit of application code to handle creating the right context based on the cognito token more below.

Rolling your own

The sea-mans approach, there are a few options here. You could use a docker environment provided from docker-lambda. This is great but it’s the same image AWS SAM uses under the hood. I didn’t see much benefit here.

You could also attempt to run your go function completely separate of any framework/docker image and cut out the middleman. Meaning you would have to replace lambda.Start in your code and also handle the cognito token when running locally.

Apex gateway and algnhsa both handle replacing lambda.Start and you get the benefit of using net/http handler signatures opposed to something in this list.

But you’re left dealing with parsing that cognito token. Sigh, as is the same case with AWS Sam you must write a middleware function that manually populates APIGatewayRequestProxy with the correct claims from the token. Then reattach the APIGatewayRequestProxy back to the request, and pass it along the middleware chain.

A quick look into how i am handling access tokens locally

Despite dealing with the issues, it is go naked, so it's fast. But the extra code and lack of Lambda environment isn’t the confidence booster we’re looking for.

Speed 🔥, Environment 💩, Extra Work 💩.

Recompilation / Watch Tools

Something I’ve yet to mention is how we actually handle the live reloading. go is a compiled language so every-time we make a change we need to recompile that code and make sure we are running those binaries. The selection of tools I’ve been looking at are:

Realize. Very popular go task runner, probably because there’s a (gimmicky) web UI. I found the config overly complex and in the end had lots of issues with go mod despite this work around. It also has issues with windows environments.

despite this work around. It also has issues with windows environments. Modd. A very easy to use and flexible tool. Cross platform.

Reflex. Another simple and easy to use tool. Probably only works on linux and mac*

Using these tools to recompile every single go binary is a bit of a tricky process. You run into issues at scale, because not only do you need to recompile handlers when they change, but also the supporting packages like utils / database models.

Below is an example with reflex, it rebuilds everything on any Go file change.

makefile for using reflex — Open in github to see the full gist & improved buildP!

make build takes about 30 seconds, to build my ~20ish functions.

The improved make buildP takes about 5 seconds, to build the same ~20ish functions but with noticeable CPU load.

Another example using modd which is less dynamic but you make up for in speed. It rebuilds individual handlers only when they change. Or it rebuilds everything when any util/helper code changes.

modd.conf final example — Open in github to see the full gist!

I think this is a nice trade off. I would love to see any other solutions people have come up with so please leave a comment below!

TLDR; Service Recap

The results are in

It looks like in 2020 it’s still going to be a mashup of tools.

I’m not going to be moving away from Serverless for deployment. Their plugins and first class support for authorizers means I can’t leave. But I’ll be using AWS Sam to run the API locally with a bit of hackney app code, cobbled with some recompilation scripts to constantly rebuild on changes!

Thanks for reading and be sure to let me know if you have any better solutions!