Two software engineers playing outside after they saved countless hours by going server-less with GraphQL. Photo by Jenn Evelyn-Ann on Unsplash

How to build an entire GraphQL backend using AWS Lambda and Postgraphile in under two seconds*

*It will actually take around an hour

The best things about writing code is not having to write very much of it. Let’s discuss how to get there using a few magical libraries and a couple of managed services from our favorite cloud provider and supreme leader of the universe, AWS.

This is not meant to be a step-by-step tutorial, but more of a guide for when you get lost in the woods. But first, let’s get on the same page. Our entire application has a few basic requirements:

Here is the basic architecture we are aiming for:

So, how are we going to do server-less GraphQL? Hold on, I’ll get there. This story starts at the user service…

1. Users are stored in Cognito

I’m not going to put the word “Cognito” in big letters because, if you used it before, you probably had a bad time. Yes, it’s documentation is notoriously bad and it’s support in CloudFormation basically non-existent at the time of this writing, but it also does a lot for us so let’s use it anyway.

The way you setup Cogntio doesn’t really matter, you just need to have a user pool; all we care about is using the user pool as the authentication provider for our API Gateway endpoint. Obviously, storing users in Cognito is an opinion with massive implications:

  • You will end up using things like AWS Amplify to interact with the service on the client-side
  • You cannot build foreign key relationships between users and other tables directly in the database
  • Access tokens are generated inside Cognito and must play by their rules, but we do get a few triggers with which we can augment them

But with all those considerations, locking down an endpoint is dangerously easy:

APIUserPool is a Cognito Pool with absolutely nothing special about it

If you really don’t want to use Cognito to handle your API Gateway authentication, that is fine. Suffice to say we just want the request to our Lambda function to include some interesting, (and unique), things about our user…which Cognito will do without any special configuration.

If you’re new to Cognito and API Gateway, here are some great places to start:

Since there are a lot of great resources on setting up API Gateway I won’t get into more detail here, but let me know in the comments if this is a topic you’d like me to cover in the future.

2. A Lambda parses our request, with magic

Now on to the Lambda function, which is best explained in gist form:

The most important part of this endpoint is postgraphile, a magical library responsible for reflecting our database as a GraphQL server automatically.

We’re going to closely follow their documented schema-only usage, which explains how to render a schema and run queries against it. On line 11 we are building the actual schema; awaiting the result of that promise will give you a GraphQLSchema instance that you can do whatever you want with. Another trick might be to print the SDL and publish it as a package, which might make you more popular with your friends and front-end developers.

Before actually running our query we do one other interesting thing: extract some variables from the authorizer.claims. These values were added by Cognito in the last step, remember? Everything in the pgSettings option on line 39 are set as local variables in the Postgres transaction so we could toss other things in here too, like groups, if we wanted to build policies off of other attributes.

From there, we can follow the reference documentation from Postgraphile to get a result for the query. Returning the results is the same as with any other Lambda invoked via API Gateway, just ensure to set your statusCode and stringify your body. For this implementation I’m also allowing CORS to make life easier.

3. Postgres runs our query, with magic

The basic premise of how we will use Postgres is simple: it manages our things by both defining their schema and controlling our access to them. The schema part is probably familiar, we have a table for things and a special table that holds their permissions, a.k.a. an access-control-list:

Table diagram for those of you who think visually, copy-pasta version available below
CREATE TABLE IF NOT EXISTS things (
id UUID DEFAULT uuid_generate_v4() NOT NULL PRIMARY KEY,
value TEXT
);
CREATE TABLE IF NOT EXISTS permissions (
thing_id UUID REFERENCES things(id),
user_id UUID
);

You’ve probably done that part before, but this next bit is where we’ll get weird.

In this gist we are doing three things:

If you’re curious to understand more about the policy, this study explains why I wrote it that way and how efficient it is.

Line 30 is a little confusing so I’ll explain further: a new item doesn’t have any permissions yet so the first policy will fail; this second policy allows everyone to insert new things without checking permissions.

The final piece is a trigger which automatically inserts permissions for each new thing. This happens after an insert which is why we needed that second policy in the last gist.

Permissions trigger

But that’s it! That’s our whole database! And our whole application! Now go play in the street, or whatever kids do these days.

For a more complete reference, you can check out this work-in-progress project which makes use of this strategy.

To Recap

We did three interesting things:

And we did it all with not very much code. Nice job.

Want more?

This is how we build things at Volta, and we’re hiring in San Francisco. Check out our open positions to connect.

I build things by breaking them.