DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Building REST API Backend Easily With Ballerina Language
  • Aggregating REST APIs Calls Using Apache Camel
  • Composite Requests in Salesforce Are a Great Idea
  • Creating a Secure REST API in Node.js

Trending

  • How to Convert XLS to XLSX in Java
  • Advancing Your Software Engineering Career in 2025
  • Navigating and Modernizing Legacy Codebases: A Developer's Guide to AI-Assisted Code Understanding
  • Navigating Change Management: A Guide for Engineers
  1. DZone
  2. Data Engineering
  3. Databases
  4. How to Implement a GraphQL API on Top of an Existing REST API

How to Implement a GraphQL API on Top of an Existing REST API

Start using GraphQL in legacy portions of your app without breaking existing contracts with functionality that still relies on the original REST API.

By 
Tyler Hawkins user avatar
Tyler Hawkins
DZone Core CORE ·
Feb. 02, 21 · Tutorial
Likes (8)
Comment
Save
Tweet
Share
6.9K Views

Join the DZone community and get the full member experience.

Join For Free
Dad joke "dadabase" app

Where do you keep your dad jokes? In a dadabase of course! Let's imagine that you are a site maintainer for the world's best dad joke database. Your app communicates with the database using a REST API that allows you to retrieve jokes and post ratings for those jokes. Visitors to your site can rate each joke they see via a simple user interface.

Recently you heard of a fancy new technology called GraphQL that provides the flexibility to request only the data that you need using a single API endpoint. It sounds neat, and you'd like to start using it in your app. But, you'd really prefer not to make any breaking changes to the existing REST API. Is it possible to support both the REST API and the GraphQL API in your app? You're about to find out!

In this article we'll explore what it takes to implement a GraphQL API on top of an existing REST API. This strategy allows you to start using GraphQL in legacy portions of your app without breaking any existing contracts with functionality that may still rely on the original REST API.

If you'd like to see the end result, you can find the code for the REST API here and the code for the front-end and GraphQL API here. Don't forget to visit the app as well to groan at some jokes.


The Initial Architecture

The app's backend was originally built using Node and JSON Server. JSON Server utilizes Express to provide a full REST API to a mock database generated from a simple JSON file. A separate Express server takes care of serving the static HTML, CSS, and JavaScript assets for the front-end. The front-end is implemented in vanilla JS and uses the browser's built-in Fetch API to make the API requests. The app is hosted on Heroku to make deployment and monitoring a breeze.

Our JSON file contains information for a few jokes as well as some ratings. It's reproduced in full below:

JSON
 




x
37


 
1
{
2
  "jokes": [
3
    {
4
      "id": 1,
5
      "content": "I don't often tell dad jokes, but when I do, sometimes he laughs."
6
    },
7
    {
8
      "id": 2,
9
      "content": "Why was the scarecrow promoted? For being outstanding in his field."
10
    },
11
    {
12
      "id": 3,
13
      "content": "What did the grape do when someone stepped on him? He let out a little whine."
14
    },
15
    {
16
      "id": 4,
17
      "content": "Einstein, Pascal, and Newton are playing hide and seek. Einstein covers his eyes and begins counting. While Pascal runs off and hides, Newton takes out some chalk and marks a square on the ground with side lengths of exactly 1 meter, then sits down inside the square. When Einstein is finished counting and sees Newton sitting on the ground, he yells, \"Ha, I've found you, Newton!\". Newton replies, \"No you haven't! You've found one Newton over a square meter. You've found Pascal!"
18
    }
19
  ],
20
  "ratings": [
21
    { "id": 1, "jokeId": 1, "score": 8 },
22
    { "id": 2, "jokeId": 2, "score": 3 },
23
    { "id": 3, "jokeId": 3, "score": 6 },
24
    { "id": 4, "jokeId": 1, "score": 7 },
25
    { "id": 5, "jokeId": 2, "score": 6 },
26
    { "id": 6, "jokeId": 3, "score": 4 },
27
    { "id": 7, "jokeId": 1, "score": 9 },
28
    { "id": 8, "jokeId": 2, "score": 10 },
29
    { "id": 9, "jokeId": 3, "score": 2 },
30
    { "id": 10, "jokeId": 4, "score": 10 },
31
    { "id": 11, "jokeId": 4, "score": 10 },
32
    { "id": 12, "jokeId": 4, "score": 10 },
33
    { "id": 13, "jokeId": 4, "score": 10 },
34
    { "id": 14, "jokeId": 4, "score": 10 },
35
    { "id": 15, "jokeId": 4, "score": 10 }
36
  ]
37
}



JSON Server takes that file as a starting point for the database and then implements a REST API that includes support for GET, POST, PUT, PATCH, and DELETE requests. The magic of JSON Server is that using this API really does modify the underlying JSON file, so the database is fully interactive. JSON Server can be started directly from an npm script without any additional setup, but in order to provide a little more configuration and a dynamic port, we can instead write a few lines of code like so:

JavaScript
 




xxxxxxxxxx
1
10


 
1
const jsonServer = require('json-server')
2
const server = jsonServer.create()
3
const router = jsonServer.router('db.json')
4
const middlewares = jsonServer.defaults()
5

          
6
server.use(middlewares)
7
server.use(router)
8
server.listen(process.env.PORT || 3000, () => {
9
  console.log(`JSON Server is running on port ${process.env.PORT || 3000}`)
10
})



You can test out our mock database by cloning the repo for the API, running npm install, and then running npm start. If you navigate to http://localhost:3000/jokes you'll see all of the jokes. Navigating to http://localhost:3000/ratings will display all the ratings.

/jokes API endpoint returns all the jokes when running the app locally

Wonderful! We can run our app's backend locally in the browser. Now let's get our API hosted on Heroku. First, we need to install the Heroku CLI. After that, we can log in, create the app, push it to Heroku, and open the new app in our browser in four easy steps:

heroku login                                # logs in to your Heroku account
heroku create dad-joke-dadabase-rest-api    # creates the Heroku app
git push heroku master                      # deploys the code to Heroku
heroku open                                 # opens the Heroku app on your machine

And look, now we have a publicly available API out on the web!

/jokes API endpoint returns all the jokes when hosting the API on Heroku


Building the User Interface

Now that we have a working REST API, we can build the front-end to consume that API and display the user interface for viewing and rating jokes. The HTML provides a shell of the page with containers into which the JavaScript will insert content for each joke.

HTML
 




xxxxxxxxxx
1
37


 
1
<!doctype html>
2
<html lang="en">
3
<head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
  <title>Dad Joke Dadabase</title>
7
  <meta name="description" content="Where do you keep your dad jokes? In a dadabase of course!">
8
  <meta name="author" content="Tyler Hawkins">
9
  <link rel="stylesheet" href="./style.css">
10
</head>
11
<body>
12
  <h1>Dad Joke Dadabase</h1>
13

          
14
  <div class="project">
15
    <h2 class="jokeContent"></h2>
16
    <div class="rateThisJokeContainer">
17
      <p>Rate this joke:</p>
18
      <div class="rateThisJokeOptions">
19
        <span class="formGroup"><input type="radio" id="score-1" name="yourRating" value="1" /><label for="score-1">1</label></span>
20
        <span class="formGroup"></span><input type="radio" id="score-2" name="yourRating" value="2" /><label for="score-2">2</label></span>
21
        <span class="formGroup"></span><input type="radio" id="score-3" name="yourRating" value="3" /><label for="score-3">3</label></span>
22
        <span class="formGroup"></span><input type="radio" id="score-4" name="yourRating" value="4" /><label for="score-4">4</label></span>
23
        <span class="formGroup"></span><input type="radio" id="score-5" name="yourRating" value="5" /><label for="score-5">5</label></span>
24
        <span class="formGroup"></span><input type="radio" id="score-6" name="yourRating" value="6" /><label for="score-6">6</label></span>
25
        <span class="formGroup"></span><input type="radio" id="score-7" name="yourRating" value="7" /><label for="score-7">7</label></span>
26
        <span class="formGroup"></span><input type="radio" id="score-8" name="yourRating" value="8" /><label for="score-8">8</label></span>
27
        <span class="formGroup"></span><input type="radio" id="score-9" name="yourRating" value="9" /><label for="score-9">9</label></span>
28
        <span class="formGroup"></span><input type="radio" id="score-10" name="yourRating" value="10" /><label for="score-10">10</label></span>
29
      </div>
30
    </div>
31
    <p class="averageRating">Average Rating: <span class="jokeRatingValue">7.8</span></p>
32
    <button id="nextJoke">See Next Joke</button>
33
</div>
34
  <script src="./script.js"></script>
35
</body>
36
</html>



The JavaScript is shown below. The key pieces that interact with the REST API are the two fetch requests. The first fetches all of the jokes from the database by hitting the /jokes?_embed=ratings endpoint. The second makes a POST request to the /ratings endpoint to submit a new rating for each joke you rate.

JavaScript
 




xxxxxxxxxx
1
64


 
1
const jokeContent = document.querySelector('.jokeContent')
2
const jokeRatingValue = document.querySelector('.jokeRatingValue')
3
const nextJokeButton = document.querySelector('#nextJoke')
4

          
5
const jokes = []
6
let currentJokeIndex = -1
7

          
8
const displayNextJoke = () => {
9
  currentJokeIndex++
10
  if (currentJokeIndex >= jokes.length) {
11
    currentJokeIndex = 0
12
  }
13

          
14
  const joke = jokes[currentJokeIndex]
15

          
16
  jokeContent.textContent = joke.content
17

          
18
  const totalScore = joke.ratings.reduce(
19
    (total, rating) => (total += rating.score),
20
    0
21
  )
22
  const numberOfRatings = joke.ratings.length
23
  const averageRating = totalScore / numberOfRatings
24

          
25
  jokeRatingValue.textContent = averageRating.toFixed(1)
26
}
27

          
28
const submitJokeRating = () => {
29
  const ratingInput = document.querySelector('input[name="yourRating"]:checked')
30

          
31
  if (ratingInput && ratingInput.value) {
32
    const score = Number(ratingInput.value)
33
    const jokeId = jokes[currentJokeIndex].id
34
    const postData = { jokeId, score }
35

          
36
    fetch('/ratings', {
37
      method: 'POST',
38
      headers: {
39
        'Content-Type': 'application/json',
40
      },
41
      body: JSON.stringify(postData),
42
    })
43
      .then(response => response.json())
44
      .then(responseData => {
45
        const jokeToUpdate = jokes.find(joke => joke.id === responseData.jokeId)
46
        jokeToUpdate && jokeToUpdate.ratings.push(responseData)
47
      })
48
      .finally(() => {
49
        ratingInput.checked = false
50
        displayNextJoke()
51
      })
52
  } else {
53
    displayNextJoke()
54
  }
55
}
56

          
57
nextJokeButton.addEventListener('click', submitJokeRating)
58

          
59
fetch('/jokes?_embed=ratings')
60
  .then(response => response.json())
61
  .then(data => {
62
    jokes.push(...data)
63
    displayNextJoke()
64
  })



Dad joke "dadabase" user interface allows you to rate each joke


Setting Up Apollo Server

So, that's the existing app architecture: a simple front-end that interacts with the database via a REST API. Now how can we begin using GraphQL? We'll start by installing apollo-server-express, which is a package that allows us to use Apollo Server with Express. We'll also install the apollo-datasource-rest package to help us integrate the REST API with Apollo Server. Then we'll configure the server by writing the following code:

JavaScript
 




xxxxxxxxxx
1
35


 
1
const express = require('express')
2
const path = require('path')
3
const { ApolloServer } = require('apollo-server-express')
4
const JokesAPI = require('./jokesAPI')
5
const RatingsAPI = require('./ratingsAPI')
6
const typeDefs = require('./typeDefs')
7
const resolvers = require('./resolvers')
8

          
9
const app = express()
10
const server = new ApolloServer({
11
  typeDefs,
12
  resolvers,
13
  dataSources: () => ({
14
    jokesAPI: new JokesAPI(),
15
    ratingsAPI: new RatingsAPI(),
16
  }),
17
})
18

          
19
server.applyMiddleware({ app })
20

          
21
app
22
  .use(express.static(path.join(__dirname, 'public')))
23
  .get('/', (req, res) => {
24
    res.sendFile('index.html', { root: 'public' })
25
  })
26
  .get('/script.js', (req, res) => {
27
    res.sendFile('script.js', { root: 'public' })
28
  })
29
  .get('/style.css', (req, res) => {
30
    res.sendFile('style.css', { root: 'public' })
31
  })
32

          
33
app.listen({ port: process.env.PORT || 4000 }, () => {
34
  console.log(`Server ready at port ${process.env.PORT || 4000}`)
35
})



As you can see, we configure Apollo Server with type definitions (typeDefs), resolvers, and dataSources. The typeDefs contain the schema for our GraphQL API. In it, we'll define types for our jokes and ratings as well as how to query and mutate them. The resolvers tell the server how to handle various queries and mutations and how those link to our data sources. And finally, the dataSources outline how the GraphQL API relates to the REST API.

Here are the type definitions for the Joke and Rating types and how to query and mutate them:

JavaScript
 




xxxxxxxxxx
1
25


 
1
const { gql } = require('apollo-server-express')
2

          
3
const typeDefs = gql`
4
  type Joke {
5
    id: Int!
6
    content: String!
7
    ratings: [Rating]
8
  }
9
  type Rating {
10
    id: Int!
11
    jokeId: Int!
12
    score: Int!
13
  }
14
  type Query {
15
    joke(id: Int!): Joke
16
    jokes: [Joke]
17
    rating(id: Int!): Rating
18
    ratings: [Rating]
19
  }
20
  type Mutation {
21
    rating(jokeId: Int!, score: Int!): Rating
22
  }
23
`
24

          
25
module.exports = typeDefs



The jokes data source defines methods for calling the original REST API endpoint to create, read, update, and delete jokes from the database:

JavaScript
 




xxxxxxxxxx
1
34


 
1
const { RESTDataSource } = require('apollo-datasource-rest')
2

          
3
class JokesAPI extends RESTDataSource {
4
  constructor() {
5
    super()
6
    this.baseURL = 'https://dad-joke-dadabase-rest-api.herokuapp.com/'
7
  }
8

          
9
  async getJoke(id) {
10
    return this.get(`jokes/${id}?_embed=ratings`)
11
  }
12

          
13
  async getJokes() {
14
    return this.get('jokes?_embed=ratings')
15
  }
16

          
17
  async postJoke(jokeContent) {
18
    return this.post('jokes', jokeContent)
19
  }
20

          
21
  async replaceJoke(joke) {
22
    return this.put('jokes', joke)
23
  }
24

          
25
  async updateJoke(joke) {
26
    return this.patch('jokes', { id: joke.id, joke })
27
  }
28

          
29
  async deleteJoke(id) {
30
    return this.delete(`jokes/${id}`)
31
  }
32
}
33

          
34
module.exports = JokesAPI



The ratings data source looks nearly identical, but with "rating" substituted for "joke" in every instance. (refer to the GitHub repo if you'd like to see the code for this.)

Finally, we set up our resolvers to show how to use the data sources:

JavaScript
 




xxxxxxxxxx
1
21


 
1
const resolvers = {
2
  Query: {
3
    joke: async (_source, { id }, { dataSources }) =>
4
      dataSources.jokesAPI.getJoke(id),
5
    jokes: async (_source, _args, { dataSources }) =>
6
      dataSources.jokesAPI.getJokes(),
7
    rating: async (_source, { id }, { dataSources }) =>
8
      dataSources.ratingsAPI.getRating(id),
9
    ratings: async (_source, _args, { dataSources }) =>
10
      dataSources.ratingsAPI.getRatings(),
11
  },
12
  Mutation: {
13
    rating: async (_source, { jokeId, score }, { dataSources }) => {
14
      const rating = await dataSources.ratingsAPI.postRating({ jokeId, score })
15
      return rating
16
    },
17
  },
18
}
19

          
20
module.exports = resolvers



With that, we have everything in place we need in order to start using our GraphQL API through Apollo Server. To get our new front-end and GraphQL API hosted on Heroku, we'll create and deploy a second app like so:

heroku create dad-joke-dadabase       # creates the Heroku app
git push heroku master                # deploys the code to Heroku
heroku open                           # opens the Heroku app on your machine


Replacing the Endpoint to Fetch Jokes

You'll recall that we have two endpoints used by the front-end: one to fetch jokes and one to post ratings. Let's swap out the REST API for our GraphQL API when we fetch the jokes. The code previously looked like this:

JavaScript
 




xxxxxxxxxx
1


 
1
fetch('/jokes?_embed=ratings')
2
  .then(response => response.json())
3
  .then(data => {
4
    jokes.push(...data)
5
    displayNextJoke()
6
  })



Now to use the GraphQL endpoint, we can write this instead:

JavaScript
 




xxxxxxxxxx
1
24


 
1
fetch('/graphql', {
2
  method: 'POST',
3
  headers: { 'Content-Type': 'application/json' },
4
  body: JSON.stringify({
5
    query: `
6
    query GetAllJokesWithRatings {
7
      jokes {
8
        id
9
        content
10
        ratings {
11
          score
12
          id
13
          jokeId
14
        }
15
      }
16
    }
17
  `,
18
  }),
19
})
20
  .then(res => res.json())
21
  .then(res => {
22
    jokes.push(...res.data.jokes)
23
    displayNextJoke()
24
  })



We can run the app locally now and verify that the user experience still works properly. In fact, from the user's point of view, nothing has changed at all. But if you look at the network requests in your browser's developer tools, you'll see that we're now fetching our jokes from the /graphql endpoint. Amazing!

The Network tab shows a request is being made to the /graphql endpoint now


Replacing the Endpoint to Submit Ratings

One API down, one to go! Let's swap out the ratings submission functionality now. The code to post a new joke rating previously looked like this:

JavaScript
 




xxxxxxxxxx
1
16


 
1
fetch('/ratings', {
2
  method: 'POST',
3
  headers: {
4
    'Content-Type': 'application/json',
5
  },
6
  body: JSON.stringify(postData),
7
})
8
  .then(response => response.json())
9
  .then(responseData => {
10
    const jokeToUpdate = jokes.find(joke => joke.id === responseData.jokeId)
11
    jokeToUpdate && jokeToUpdate.ratings.push(responseData)
12
  })
13
  .finally(() => {
14
    ratingInput.checked = false
15
    displayNextJoke()
16
  })



To use our GraphQL API, we'll now use the following:

JavaScript
 




xxxxxxxxxx
1
25


 
1
fetch('/graphql', {
2
  method: 'POST',
3
  headers: { 'Content-Type': 'application/json' },
4
  body: JSON.stringify({
5
    query: `
6
    mutation CreateRating {
7
      rating(jokeId: ${jokeId}, score: ${score}) {
8
        id
9
        score
10
        jokeId
11
      }
12
    }
13
  `,
14
  }),
15
})
16
  .then(res => res.json())
17
  .then(res => {
18
    const rating = res.data.rating
19
    const jokeToUpdate = jokes.find(joke => joke.id === rating.jokeId)
20
    jokeToUpdate && jokeToUpdate.ratings.push(rating)
21
  })
22
  .finally(() => {
23
    ratingInput.checked = false
24
    displayNextJoke()
25
  })



A quick test gives us some promising results. Once again, the user experience remains unchanged, but now we're fully using the /graphql endpoint for both our requests!


Conclusion

We did it! We successfully wrote a GraphQL API endpoint on top of an existing REST API. This allows us to use GraphQL to our heart's content without breaking existing functionality and without modifying the original REST API. Now we can deprecate the REST API or get rid of it completely at a later date.

While our dad joke database is entirely fictional, nearly every technology company that existed prior to GraphQL's release in 2015 will find themselves in this same position of migrating to GraphQL if and when they choose to do so. The good news is that Apollo Server is flexible enough to pull data from a variety of sources, including existing REST API endpoints.

API REST Web Protocols GraphQL Database app

Published at DZone with permission of Tyler Hawkins. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Building REST API Backend Easily With Ballerina Language
  • Aggregating REST APIs Calls Using Apache Camel
  • Composite Requests in Salesforce Are a Great Idea
  • Creating a Secure REST API in Node.js

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!