DEV Community

Cover image for Learn how YOU can build a Serverless GraphQL API on top of a Microservice architecture, part I
Chris Noring for Microsoft Azure

Posted on • Edited on • Originally published at softchris.github.io

Learn how YOU can build a Serverless GraphQL API on top of a Microservice architecture, part I

Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris

The idea with this article is to show how we can build microservices, dockerize them and combine them in a GraphQL API and query it from a Serverless function, how's that for a lot of buzzwords in one? ;) Microservices, Docker, GraphQL, Serverless

This is part of series:

So, it's quite ambitious to create Microservices, Serverless and deploy to the Cloud in one article so this is a two-parter. This is part one. This part deals with Microservices and GraphQL. In part two we make it serverless and deploy it.

In this article we will cover:

  • GraphQL and Microservices, these are two great paradigms that really go quite well together, let's explain how
  • Building Microservices and Dockerizing them, let's create two micros services, each with their own areas of responsibility and let's Dockerize them
  • Implementing GraphQl, we will show how we can define a GraphQL backend with schema and resolver and how to query it, of course, we will also tie in our Micro services here

Resources

We will throw you in head first using Docker, GraphQL and some Serverless with Azure functions. This article is more of a recipe of what you can do with the above techniques so if you feel you need a primer on the above here is a list of posts I've written:

 GraphQL and Microservices

A library and paradigm like GraphQL, is the most useful when it is able to combine different data sources into one and serve that up as one unified API. A developer of the Front end app can then query the data they need by using just one request.

Today it becomes more common to break down a monolithic architecture into microservices, thereby you get many small APIs that works independently. GraphQL and microservices are two paradigms that go really well together. How you wonder? GraphQL is really good at describing schemas but also stitch together different APIs and the end result is something that's really useful for someone building an app as querying for data will be very simple.

Different APIs is exactly what we have when we have a Microservices architecture. Using GraphQL on top of it all means we can reap the benefits from our chosen architecture at the same time as an App can get exactly the data it needs.

How you wonder? Stay with me throughout this article and you will see exactly how. Bring up your code editor cause you will build it with me :)

 The plan

Ok, so what are we building? It's always grateful to use an e-commerce company as the target as it contains so many interesting problems for us to solve. We will zoom in on two topics in particular namely products and reviews. When it comes to products we need a way to keep track of what products we sell and all their metadata. For reviews we need a way to offer our customer a way to review our products, give it a grade, a comment and so on and so forth. These two concepts can be seen as two isolated islands that can be maintained and developed independently. If for example, a product gets a new description there is no reason that should affect the review of said product.

Ok, so we turn these two concepts into product service and a review service.

Data structure for the services

What does the data look like for these services? Well they are in their infancy so let's assume the product service is a list of products for now with a product looking something like this:



[{
  id: 1,
  name: 'Avengers - infinity war',
  description: 'a Blue-ray movie'
}]


Enter fullscreen mode Exit fullscreen mode

The review service would also hold data in a list like so:



[{
  id: 2,
  title: 'Oh snap what an ending',
  grade: 5,
  comment: 'I need therapy after this...',
  product: 1
}]


Enter fullscreen mode Exit fullscreen mode

As you can see from the above data description the review service holds a reference to a product in the product service and it's by querying the product service that you get the full picture of both the review and the product involved.

 Dockerizing

Ok, so we understand what the services need to provide in terms of a schema. The services also need to be containerized so we will describe how to build them using Docker a Dockerfile and Docker Compose.

 GraphQL

So the GraphQL API serves as this high-level API that is able to combine results from our product service as well as review service. It's schema should look something like this:



type Product {
   id: ID,
   name: String,
   description: String
}

type Review {
  id: ID,
  title: String,
  grade: Int,
  comment: String,
  product: Product
} 

type Query {
  products: [Product]
  reviews: [Review]
 }


Enter fullscreen mode Exit fullscreen mode

We assume that when a user of our GraphQL API queries for Reviews they want to see more than just the review but also some extra data on the Product, what's it called, what it is and so on. For that reason, we've added the product property on the Review type in the above schema so that when we drill down in our query, we are able to get both Review and Product information.

 Serverless

So where does Serverless come into this? We need a way to host our API. We could be using an App Service but because our GraphQL API doesn't need to hold any state of its own and it only does a computation (it assembles a result) it makes more sense to make it a light-weight on-demand Azure Function. So that's what we are going to do :) As stated in the beginning we are saving this for the second part of our series, we don't want to bore you by a too lengthy article :)

Creating and Dockerizing our Microservices

We opt for making these services as simple as possible so we create REST APIs using Node.js and Express, like so:



/products
  app.js
  Dockerfile
  package.json
/reviews
  app.js
  Dockerfile
  package.json


Enter fullscreen mode Exit fullscreen mode

The app.js file for /products looks like this:



// products/app.js

const express = require('express')
const app = express()
const port = process.env.PORT || 3000

app.get('/', (req, res) => res.json([{
  id: "1",
  name: 'Avengers - infinity war',
  description: 'a Blue ray movie'
}]))
app.listen(port, () => console.log(`Example app listening on port port!`))


Enter fullscreen mode Exit fullscreen mode

and the app.js for /reviews looks like this:




// reviews.app.js

const express = require('express')
const app = express()
const port = process.env.PORT  || 3000

app.get('/', (req, res) => res.json([{
  id: "2",
  title: 'Oh snap what an ending',
  grade: 5,
  comment: 'I need therapy after this...',
  product: 1
}]))
app.listen(port, () => console.log(`Example app listening on port port!`))


Enter fullscreen mode Exit fullscreen mode

Looks almost the same right? Well, we try to keep things simple for now and return static data but it's quite simple to add database later on.

Dockerizing

Before we start Dockerizing we need to install our dependency Express like so:



npm install express


Enter fullscreen mode Exit fullscreen mode

This needs to be done for each service.

Ok, we showed you in the directory for each service how there was a Dockerfile. It looks like this:



// Dockerfile

FROM node:latest
WORKDIR /app
ENV PORT=3000
COPY . .
RUN npm install
EXPOSE $PORT
ENTRYPOINT ["npm", "start"]


Enter fullscreen mode Exit fullscreen mode

Let's go up one level and create a docker-compose.yaml file, so it's easier to create our images and containers. Your file system should now look like this:



docker.compose.yaml
/products
  app.js
  Dockerfile
  package.json
/reviews
  app.js
  Dockerfile
  package.json


Enter fullscreen mode Exit fullscreen mode

Your docker-compose.yaml should have the following content:



version: '3.3'
services: 
  product-service:
    build:
      context: ./products
    ports:
      - "8000:3000"
    networks: 
      - microservices
  review-service:
    build:
      context: ./reviews
    ports:
      - "8001:3000"
    networks:
      - microservices
networks: 
  microservices:


Enter fullscreen mode Exit fullscreen mode

We can now get our service up and running with



docker-compose up -d


Enter fullscreen mode Exit fullscreen mode

I always feel like I'm starting up a jet engine when I run this command as all of my containers go up at the same time, so here goes, ignition :)

You should be able to find the products service at http://localhost:8000 and the reviews service at http://localhost:8001. That covers the microservices for now, let's build our GraphQL API next.

Your products service should look like the following:

and your review service should look like this:

 Implementing GraphQL

There are many ways to build a GraphQL server, we could be using the raw graphql NPM library or the express-graphql, this will host our server in a Node.js Express server. Or we could be using the one from Apollo and so on. We opt for the first one graphql as we will ultimately serve it from a Serverless function.

So what do we need to do:

  1. Define a schema
  2. Define services we can use to resolve different parts of our schema
  3. Try out the API

Define a schema

Now, this is an interesting one, we have two options here for defining a schema, either use the helper function buildSchema() or use the raw approach and construct our schema using primitives. For this case, we will use the raw approach and the reason for that is I simply couldn't find how to resolve things at depth using buildSchema() despite reading through the manual twice. It's strangely enough easily done if we were to use express-graphql or Apollo so sorry if you feel your eyes bleed a little ;)

Ok, let's define our schema first:



// schema.js

const { 
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLInt,
  GraphQLNonNull,
  GraphQLList,
  GraphQLString
} = require('graphql');

const {
  getProducts,
  getReviews,
  getProduct
} = require('./services');

const reviewType = new GraphQLObjectType({
  name: 'Review',
  description: 'A review',
  fields: () => ({
    id: {
      type: GraphQLNonNull(GraphQLString),
      description: 'The id of Review.',
    },
    title: {
      type: GraphQLString,
      description: 'The title of the Review.',
    },
    comment: {
      type: GraphQLString,
      description: 'The comment of the Review.',
    },
    grade : {
      type: GraphQLInt
    },
    product: {
      type: productType,
      description: 'The product of the Review.',
      resolve: (review) => getProduct(review.product) 
    }
  })
})

const productType = new GraphQLObjectType({
  name: 'Product',
  description: 'A product',
  fields: () => ({
    id: {
      type: GraphQLNonNull(GraphQLString),
      description: 'The id of Product.',
    },
    name: {
      type: GraphQLString,
      description: 'The name of the Product.',
    },
    description: {
      type: GraphQLString,
      description: 'The description of the Product.',
    }
  })
});

const queryType = new GraphQLObjectType({
  name: 'Query',
  fields: () => ({
    hello: {
      type: GraphQLString,
      resolve: (root) => 'world'
    },
    products: {
      type: new GraphQLList(productType),
      resolve: (root) => getProducts(),
    },
    reviews: {
      type: new GraphQLList(reviewType),
      resolve: (root) => getReviews(),
    }
  }),
});

module.exports = new GraphQLSchema({
  query: queryType,
  types: [reviewType, productType],
});


Enter fullscreen mode Exit fullscreen mode

Above we are defining two types Review and Product and we expose two query fields products and reviews.

I want you to pay special attention to the variable reviewType and how we resolve the product field. Here we are resolving it like so:



resolve: (review) => getProduct(review.product) 


Enter fullscreen mode Exit fullscreen mode

Why do we do that? Well, it has to do with how data is stored on a Review. Let's revisit that. A Review stores its data like so:



{
  title: ''
  comment: '',
  grade: 5
  product: 1
}


Enter fullscreen mode Exit fullscreen mode

As you can see above the product field is an integer. It's a foreign key pointing to a real product in the product service. So we need to resolve it so the API can be queried like so:



{
  reviews {
    product { 
      name
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

If we don't resolve product to a product object instead the above query would error out.

Create services

In our schema.js we called methods like getProducts(), getReviews() and getProduct() and we need those to exist so we create a file services.js, like so:



const fetch = require('node-fetch');

const getProducts = async() => {
  const res = await fetch(process.env.PRODUCTS_URL)
  const json = res.json();
  return json;
}

const getProduct = async(product) => {
  const products = await getProducts()
  return products.find(p => p.id == product);
} 

const getReviews = async() => {
  const res = await fetch(process.env.REVIEW_URL)
  const json = res.json();
  return json;
}

module.exports = {
  getProducts,
  getReviews,
  getProduct
}


Enter fullscreen mode Exit fullscreen mode

Ok, we can see above that methods getProducts() and getReviews() makes HTTP requests to URL, at least judging by the names process.env.PRODUCTS_URL and process.env.REVIEW_URL. For now, we have created a .env file in which we create those two env variables like so:



PRODUCTS_URL = http://localhost:8000
REVIEW_URL = http://localhost:8001


Enter fullscreen mode Exit fullscreen mode

Wait, isn't that? Yes, it is. It is the URLs to product service and review service after we used docker-compose to bring them up. This is a great way to test your Microservice architecture locally but also prepare for deployment to the Cloud. Deploying to the Cloud is almost as simple as switching these env variables to Cloud endpoints, as you will see in the next part of this article series :)

Trying out our GraphQL API

Ok, so we need to try our code out. To do that let's create an app.js file in which we invoke the graphql() function and let's provide it our schema and query, like so:



const { graphql } = require('graphql');
const rawSchema = require('./raw-schema');
require('dotenv').config()

const query = `{ hello products { name, description } reviews { title, comment, grade, product { name, description } } }`;

graphql({
  schema: rawSchema,
  source: query
}).then(result => {
  console.log('result', result);
  console.log('reviews', result.data.reviews);
})


Enter fullscreen mode Exit fullscreen mode

In the above code, we specify a query and we expect the fields hello, products and reviews to come back to us and finally we invoke graphql() that on the then() callback serves up the result. The result should look like this:

Summary

We set out on a journey that would eventually lead us to the cloud. We are not there yet but part two will take us all the way. In this first part, we've managed to create microservices and dockerize them. Furthermore, we've managed to construct a GraphQL API that is able to define a schema that merges our two APIs together and serve that up.

What remains to do, that will be the job of the second part, is to push our containers to the cloud and create service endpoints. When we have the service endpoints we can replace the value of environment variables to use the Cloud URLs instead of the localhost ones we are using now.

Lastly, we need to scaffold a serverless function but that's all in the next part so I hope you look forward to that :)

Top comments (33)

Collapse
 
kayis profile image
K

Does Azure has an equivalent of AWS AppSync?

Collapse
 
softchris profile image
Chris Noring

Not sure. Have to check that with my colleagues. Here is a comparison page we put together though, service by services comparing AWS and Azure, docs.microsoft.com/en-us/azure/arc...

Collapse
 
softchris profile image
Chris Noring

ok, the answer is we don't have anything exactly like AppSync but we are actively working on building something like it

Collapse
 
rlimberger profile image
Rene Limberger

Hi Chris, any update on this? Thanks.

Thread Thread
 
softchris profile image
Chris Noring

Hi Rene. Still early days.. We are working generally on making it easier to use our services with JavaScript though, here is an example where you start with a NestJS app and turn that into serverless and you can easily hook in data sources into it dev.to/azure/build-your-first-serv..., have a look here as well dev.to/azure/introducing-nosql-azu... and here's a deployment tool to make serverless deployment easier github.com/manekinekko/hexa

Thread Thread
 
rlimberger profile image
Rene Limberger

Thanks Chris. I was most interested in an Azure equivalent to AWS AppSync. I am aware of Hasura but we would be more comfortable with a GraphQL PaaS that's offered natively by Azure. Any ETA on that? Thanks.

Thread Thread
 
softchris profile image
Chris Noring

I'll find out

Thread Thread
 
twilliamsphd profile image
Tiffany Williams

I'm also interested in this (Azure equivalent to AWS AppSync). Any update on the ETA?

Thread Thread
 
softchris profile image
Chris Noring

In progress. I'm sorry I can't give you good timeline but work is being done

Thread Thread
 
highmaturity profile image
Ricardo Garza

I am super interested in this feature you have an aproxímate ETA?

Thread Thread
 
rpostulart profile image
rpostulart

Is there already more to tell about the same on Azure?

Collapse
 
greg_r_ profile image
Greg

trying follow along and getting error with graphql api's app.js....

graphql({
^

ReferenceError: graphql is not defined
at Object. (/Users/me/devl/projects/devto/graphql.api/app.js:6:1)

Is there source code I can compoare to see if I'm doing something wrong?

Collapse
 
softchris profile image
Chris Noring

hi Greg. Sorry you got stuck.. Yes you need a const { graphql } = require('graphql'), at the top, also ensure you have run npm init -y followed by npm install graphql at the root directory of this file

Collapse
 
greg_r_ profile image
Greg

thanks, that worked. enjoyed the article.

Collapse
 
pastorsi profile image
Simon Measures

Thanks Chris. This looks like it's got potential. I think there's a presentation problem with your tutorial though.
We get "The Plan" and then it wanders on without giving us any distinct "Now start by doing this!"
At the moment I can't distinguish the point where you've stop outlining the plan and start telling us step one in actioning the plan. Could you help us by giving it a bit more structure, fleshing it out a bit more and guiding us through the build in a more step by step form.

Collapse
 
softchris profile image
Chris Noring

hi Simon, thanks for this comment. I'll definitely see how I can give it better structure/headlines. I'll write another comment once updated

Collapse
 
pastorsi profile image
Simon Measures

That will be brilliant. It's really good of you to respond. Thanks!

Thread Thread
 
softchris profile image
Chris Noring

of course, thank you for making the article better :)

Collapse
 
trevheath profile image
Trevor Heath

Thanks Chris! Great article. You should check out our Marvel GraphQL wrapper! Marvelql.com . The first query is a bit slow due to the official REST APIs shortcomings but we have implemented a cacheing strategy to speed up subsequent results. Enjoy!

Collapse
 
softchris profile image
Chris Noring

That's api.marvelql.com/ right?

Collapse
 
trevheath profile image
Trevor Heath

Yup! Github is here: github.com/Novvum/MarvelQL . Going to improve it quite a bit in the next month. Would love any feedback!

Collapse
 
softchris profile image
Chris Noring

Thanks for that Trev. Will check it out. SWAPI GraphQL is one of my favorites, this sounds fun too :)

Collapse
 
vladejs profile image
Vladimir López Salvador

"We are not there yet but part two will take us all the way"

Or...

Create a now.json file, configure it accordingly and... now

That's it, part 2 done :)

Collapse
 
softchris profile image
Chris Noring

well no.. the docker images needs to be pushed up.. before that can be done we need a resource group, a container registry. Then we need to create a service endpoint from each container.. and then we can replace each URL with a real one... and we need the serverless part too :) .

Collapse
 
vladimirnovick profile image
Vladimir Novick

Nice series, but I would suggest to follow more 3factor.app architecture approach. For GraphQL part you can use hasura.io free and open source engine. Engine will run in docker container and will connect to Postgres database auto generating GraphQL API for you. So you won't need to write any schema at all. And while the engine won't be serverless by itself it can connect to event triggers which can be connected to serverless functions. Moreover, GraphQL server you've created can be used inside the engine and stitch GraphQL schema together.

Would love to collaborate on doing some cool Azure and Hasura stuff. If you are interested, reach me out on twitter.com/VladimirNovick

Collapse
 
realflowcontrol profile image
Florian Engelhardt

Thanks Chris for this very inspiring article. As i am a PHP-Developer, i ported this to PHP, just for the sake of it ;-)

Collapse
 
softchris profile image
Chris Noring

Happy to hear that. Thanks for the link :)

Collapse
 
egemen profile image
ege • Edited

Hi Chris, Thank you for this awesome post. Just a quick remark:

I have added

"scripts": { "start": "node app.js" }

to package.json file for both APIs. So "npm start" will be executed successfully.

Cheers,

Collapse
 
softchris profile image
Chris Noring

hi.. Thank you for this comment. I'll make sure to add this info

Collapse
 
mitchelfelske profile image
mitchelfelske

Hi Chris,

Thank you for this awesome article! Simple and straight to the point. I am just starting with microservices and graphql stuff and I was able to easily follow this tutorial. :)

Collapse
 
softchris profile image
Chris Noring

Happy to hear that :) Thank you for your comment

Collapse
 
leonblade profile image
James Stine

You have a spelling error with “Trying out or GraphQL. Thanks for the great post! Looking forward to part two.

Collapse
 
softchris profile image
Chris Noring

thanks James :)