Secure GraphQL APIs in minutes with Cloud Run and GRAND Stack
Google Cloud recently announced Cloud Run at the NEXT ’19 conference, and I wanted to kick the tires. Cloud Run lets you run stateless containers easily, with Google managing infrastructure and scaling for you. You can use them in a way that is similar to Google Cloud Functions, except you can bring any runtime you like, because you’re deploying a Docker container.
I thought I’d give Cloud Run a shot as a serverless backend for a GraphQL application, and it turned out to be pretty easy!
This picture above shows what we’re going to be setting up. Starting with a schema, we want to set up a GraphQL microservice. Users issue queries, which get handled by a serverless backend running on Cloud Run. That backend uses a Neo4j database to store the data. The whole setup is protected by an OAuth2 layer.
What’s GraphQL? It is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools. (GraphQL.org)
And it’s going to be relatively straightforward, I promise, because Google is going to do a lot of it for us. Let’s get started!
Step 1: Make a Database
We’re going to need a place to store our data. I launched Neo4j Enterprise on GCP Marketplace. Because Neo4j is a native graph database, it’s particularly well suited for backing GraphQL APIs, following the GRAND Stack pattern.
Setting up the database just requires filling in a few values and launching it into the GCP project of your choosing. Documentation on how to do that can be found here. After a few minutes, my deployment finishes, and I have an endpoint URL, username, and password for my database.
A note on Kubernetes: Neo4j is also available for GKE, and of course Cloud Run allows us to deploy containers into an existing cluster if we wish. This is a good option, but for simplicity of setup, we’ll go with VMs today so that we don’t need to create and configure a GKE cluster.
Step 2: Create a Cloud Run Service
Let’s head over to the Cloud Run console, and click the “Create Service” button.
I’m using a small utility container I built for this purpose called Plumbline. This container is basically just a Dockerfile wrapper around a simple node.js program which uses neo4j-graphql-js to connect to Neo4j. This code manages the GraphQL layer and translates user queries into the underlying database query language (Cypher) that Neo4j uses. The container image URL we are deploying is my public version, gcr.io/neo4j-k8s-marketplace-public/plumbline:0.0.1
, but of course you could build it from source yourself, or use any other container.
We only needs a list of GraphQL typedefs, and some connection information for our new database in order to work. Neo4j-graphql-js handles everything else, including automatically adding accessors and mutators to our schema.
Neo4j-graphql-js is very convenient because it lets us focus on writing a simple domain model, and most of the boilerplate gets handled automagically.
Here’s a small set of GraphQL typedefs. We’ll be starting with a totally empty graph, and will want to create our information along this model:
type Person {
name: String!
knows: [Person] @relation(name: "KNOWS", direction: "OUT")
likes: [Hobby] @relation(name: "LIKES", direction: "OUT")
}type Hobby {
name: String!
liked_by: [Person] @relation(name: "LIKES", direction: "IN")
}
Simple enough; we have named people who can know other people, and who can also have hobbies that they like. A graph this simple could help us drive recommendations of who to introduce to whom, because of shared interests!
Those @relation
directives are cues to neo4j-graphql-js on how to store the information in question. Our “Person” items will be nodes in the graph, and we’ll store “KNOWS” relationships in Neo4j to handle the relationships. If you want the full details on how all of this works, consult the neo4j-graphql-js documentation.
Step 3: Configure the Service to serve up the GraphQL API
Next, in our “Create a service” screen, we need to set environment variables, which are initially hidden behind the “Show Optional Settings” button.
This is letting our container know how to connect to our database, and what set of typedefs should be used to create the GraphQL API.
Then click create! A few moments later:
Authentication and Authorization
Since we created a database, and we’re getting ready to both read and write data, exposing such an API publicly to the world isn’t a good idea. Cloud Run allows us to expose endpoints publicly if we want, but we didn’t take this option.
The default with Cloud Run is that you get an OAuth2 layer for free, that is backed by the same permissions that your Google Cloud project has. By generating an OAuth2 “Bearer Token” using the regular gcloud commands, you can authenticate to the endpoint we just created.
This is very convenient, because it saves us from having to have extra authentication logic in the docker container itself! Google has also provided documentation on how your container can use that token to get information about the user.
If you’re wondering how to get the token that will let you authenticate to your endpoint, it’s like this:
export TOKEN=$(gcloud config config-helper --format 'value(credential.id_token)' --force-auth-refresh)
Step 4: Use our new API!
For simplicity, you can use curl
to use our new API, which has an HTTPS endpoint. In order to work, curl needs to send the bearer token to authorize against our new service, and also needs to set a few extra headers.
Here’s a curl example of how we can use our API:
curl $ENDPOINT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization: Bearer $TOKEN" --data-binary '{"query":"mutation {\n CreatePerson(name:\"Will\"){\n name\n }\n}\n"}'
Actually formatting our GraphQL queries as JSON for curl though gets old quickly, so I used an Apollo Launchpad to issue my queries. If you’re trying this at home, you need to make sure your Launchpad, wherever you run it, includes that bearer token, or you’ll just get unauthorized errors.
Let’s create a person and a hobby. These mutators exist courtesy of neo4j-graphql-js mentioned above, and are auto-generated to go with our nice GraphQL typedefs.
Let’s create another person, “Will”, and assert that David likes Guitar.
Will likes Guitar too….
Finally, we’ve got a simple graph in place, and we can use a regular GraphQL query to check that our data is there:
Looking into the backend on the Neo4j browser side, we can see the same, using Cypher.
Conclusion
Cloud run is a great way to deploy a serverless backend that’s easy to manage and use. Combining neo4j-graphql-js and Neo4j on Google Marketplace, you can stand up a full-CRUD GraphQL API really quickly, and use it as a microservice as part of a larger solution.
From here, we can:
- Build a React application to match people based on common interests, using our back-end API
- Have other microservices call this one as needed to provide graph storage to a larger application