GraphQL is a great technology for microservices because you can use it as a data layer for these microservices.
One use case is to combine the data from all these services into a single, unified API.
But building this data layer can be very time-consuming since you need to connect the microservices into one schema. In this post, you'll learn step-by-step how to build a data layer for microservices with GraphQL in a declarative way.
TL;DR: The complete source code for this post can be found here.
Whenever you have a microservices architecture, you often don’t want to expose this architecture to the (front-end) clients that consume it.
But you use a data layer (or gateway) that serves as the entry point to your microservices.
This way you don’t give any critical information about your microservice orchestration away, which makes it less prone to hackers.
By having one single entry to your microservices, you reduce the number of requests from your clients to these services. Calls to-- for example-- your authentication service to verify a token before every request, don’t have to be repeated from the client.
Also, the data layer saves you (and others consuming your APIs) time documenting and specifying all the APIs from the microservices.
The microservices that are consumed through this data layer don’t necessarily have to use GraphQL themselves. By using a tool called StepZen, you can create a data layer for REST microservices, without having to write any code.
Instead, you can connect the microservices using GraphQL SDL (Schema Design Language) only. For every microservice, a GraphQL schema must be created, which can then be combined together into one GraphQL schema with StepZen. Let’s see how this would work.
Now you’ve learned why a data layer is helpful for your microservices, it’s time to learn how you can build such a data layer.
There are many ways to create a data layer by creating a custom GraphQL server, but this is very time-consuming and error-prone. Luckily, the tool StepZen makes it easier to create a GraphQL data layer (or server) by writing GraphQL SDL only.
By using this declarative approach you don’t only save time writing code, but it also means you don’t have to handle the deployment of the data layer yourself. StepZen deploys the data layer for you automatically on every save.
Let’s create the data layer, based on three (mocked) REST microservices that you can find in this Github repository.
The README of the project has all the information on running the mock REST microservices, by using npm
or yarn
.
First, you need to clone the repository to your local machine and install the JavaScript dependencies. After which you can run either npm start
or yarn start
.
The REST microservices can then be found on localhost
ports 3001
to 3003
, but also through an HTTPS tunnel. StepZen needs your microservices to be available though HTTPS, and therefore I use the tool Localtunnel. You don’t have to install this tool separately, as it was already installed from the projects’ dependencies.
After starting the project, something like this will be logged to your terminal:
Posts service is running on https://lorem-mouse-38.loca.lt
Users service is running on https://smart-robin-23.loca.lt
Auth service is running on https://mouse-trap-12.loca.lt
What is logged to terminal are the hostnames for the HTTPS tunnels to the mocked REST microservices.
The Posts service in example can be visited in the browser at http://localhost:3003/api/posts
, but also via the HTTPS tunnel at https://lorem-mouse-38.loca.lt/api/posts
.
You need to add these hostnames to the file config.yaml
for every service, so StepZen can use them to create the data layer:
## config.yaml
configurationset:
- configuration:
name: auth_service
client_id: test
client_secret: test123
hostname: 'mouse-trap-12.loca.lt'
- configuration:
name: posts_service
hostname: 'lorem-mouse-38.loca.lt'
- configuration:
name: users_service
hostname: 'smart-robin-23.loca.lt'
We continue by create a GraphQL schema for the services that are now connected through the HTTPS tunnel.
First, we’ll create a GraphQL schema for the authentication service.
This service is available on the endpoint /api/token
, and the hostname that was generated by Localtunnel - which is different every time you start the microservices. From this endpoint you can create an authorization token using OAuth.
The OAuth flow for client credentials is applied, which lets you create a token by calling https://$hostname/api/token?grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret
. The authentication service will return a Bearer token if the values for $client_id
and $client_secret
are correct. The GraphQL schema to reflect the authentication service can be found in services/auth/index.graphql
:
## /services/auth/index.graphql
type Auth {
id: Int!
access_token: String!
}
type Query {
token: Auth
@rest(
endpoint: "https://$hostname/api/token?grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret"
configuration: "auth_service"
)
}
The query called token
is using the custom directive @rest
, which can be used to define a REST data source that will be called by StepZen. From the file config.yaml
, the values for $client_id
and $client_secret
will be retrieved.
This query from the authentication service is how the other microservices can get the authorization token needed in the OAuth flow.
For the microservice that gets posts, it means it should have the token before it’s able to retrieve the posts. The schema for the posts service is in services/posts/index.graphql
:
## /services/posts/index.graphql
type Post {
id: Int!
title: String!
imageUrl: String!
author: Int
}
type Query {
posts(access_token: String!): [Post]
@rest(
endpoint: "https://$hostname/api/posts"
headers: [{ name: "Authorization", value: "Bearer $access_token" }]
configuration: "posts_service"
)
getPosts: [Post] @sequence(steps: [{ query: "token" }, { query: "posts" }])
}
The schema for the posts service has two queries: one to retrieve the posts, and one to retrieve the token and then get the posts. A custom directive called @sequence
is used to combine the posts
query from this GraphQL schema with the token
query from the authentication service.
The response of the token
query is used as input for the posts
query, which allows you to return the posts when the token can be retrieved.
You can reuse this setup for the users service, that also has a query to get the users with the token. The schema for this REST microservice can be found in /services/users/index.graphql
:
## /services/users/index.graphql
type User {
id: Int!
name: String!
}
type Query {
user(id: Int!, access_token: String!): User
@rest(
endpoint: "https://$hostname/api/users/$id/"
headers: [{ name: "Authorization", value: "Bearer $access_token" }]
configuration: "users_service"
)
getUser(id: Int!): User
@sequence(steps: [{ query: "token" }, { query: "user" }])
}
The GraphQL schemas for the three REST microservices in this project need be combined into one GraphQL schema. This happens in index.graphql
:
## index.graphql
schema @sdl(files: ["services/auth/index.graphql", "services/posts/index.graphql", "services/users/index.graphql"]) {
query: Query
}
Finally, in the file stepzen.config.json
the endpoint to which the GraphQL data layer will be deployed is defined. In this project it’s set to /api/datalayer
, meaning when you run the command to start (and deploy) the GraphQL server the GraphiQL playground becomes locally available at http://localhost:5000/api/datalayer.
You can run stepzen start
to combine the GraphQL schemas from all the REST microservices, which will then be deployed to StepZen:
After running this command you can start developing applications that consume this GraphQL data layer instead of the microservices directly.
Great work! You’ve learned how to create a GraphQL data layer for REST microservices, using GraphQL SDL only.
This is done with the tool StepZen, which has a generous free tier. With StepZen you cannot only create a data layer for REST microservices, but you can also create individual GraphQL APIs. You can learn more about this by looking through StepZen documentation.
Previously published here.