How to Migrate a REST API to GraphQL the Smart Way with StepZen

How GraphQL might be a better fit for your next project than RESTful APIs

April 7th, 2022

Image of laptop with StepZen and GraphQL logo

With the shift away from monolithic apps, REST APIs have been the go-to approach to standardize how the different moving parts in a system work together and share data. However, REST is also an inflexible, often ambiguous standard (it's only a set of guidelines) with several architectural and implementation issues, and has proven to scale poorly with modern client needs - which are data-intensive and rapidly changing.

This is exactly what GraphQL was created to solve! Bringing flexibility and efficiency to the table, and making working with large amounts of data a cakewalk.

In this article, we'll take a look at how GraphQL might just be a better fit for your next project than RESTful APIs, and as an example, convert an existing REST API to GraphQL - by using the free tool StepZen.

How GraphQL Improves On REST APIs

For all but the simplest implementations, REST APIs by their very design cause the client to either:

  1. Download more data than is actually needed  - because the API response might also include extraneous data useless to the specific client,

  2. Or not enough  - causing the client to hammer the API server with more requests to get the data it needs (the first GET to fetch N results + one GET for each of these N results, for a total of 1+N requests)

Either way, we'll be left dealing with high bandwidth usage  - both server-side and client-side  - and that's bad news since most web app consumption is on 4G/5G i.e. metered connections.

GraphQL solves these issues out-of-the-box by using a declarative querying syntax. Since each client using the GraphQL API has the ability to specify the precise data they need, the API delivers only that data  - no more, no less**  - **cutting down the size of payloads tremendously. Plus, clients actually have access to the relations defined among different data, and can get all relationships in one go without needing 1+N requests, or programming to batch or parallelize API calls.

Imagine REST APIs...that also let you transfer your SQL querying skills.

Then there are the ancillary benefits: GraphQL is strictly typed, and all the types that are exposed in the API are defined in the schema. Now you can decouple the frontend from the backend, so frontend teams can easily test their applications by mocking the required data structures, and backend teams can design off of an unambiguous, singular bible i.e. the schema. Even better, the schema itself can serve as documentation for your API, saving hours of tedious work.

So Where Does StepZen Fit In?

Great! We'll just use GraphQL for everything then! Well, GraphQL is awesome, but it is also a relatively new technology, and most public APIs out there don't support it. Migrating a REST API to GraphQL isn't trivial either, and comes with its own set of issues.

Fear not, here's StepZen to the rescue!

Simply put, StepZen is a GraphQL abstraction layer for all your data sources, doing tons under the hood and making it trivially easy for you to combine relational databases, REST APIs, other GraphQL APIs, whatever you've got, into a single GraphQL endpoint that you can then host on a serverless deployment.

You get all the benefits of GraphQL, married with all the benefits StepZen provides as-a-service  - real-time operating capabilities, monitoring, metrics, caching, server infrastructure, scalability, ensuring uptime  - saving you tons of time in setting up a scalable, well-designed, high-performance GraphQL API.

Case Study: Migrating a REST API to GraphQL via StepZen

So with all that out of the way, let's take a look at how I leveraged StepZen here. I often use the RandomUser.me API  - which is a sort of lorem ipsum generator for people  - to mock data for testing frontends. There are ways around its REST-specific drawbacks, but they make my API calls more verbose and complex, and that's overkill for something as simple as this. So I migrated it to GraphQL via StepZen, improving response times and cutting my bandwidth usage by nearly 70%!

Payload size difference between REST and GraphQL versions of the same API. This effect is amplified further in real-world projects where payload sizes can be in the MegaBytes range for each page of displayed results, and that's not even counting infinite scrolling and static assets!

My setup is pretty simple. I have an API proxy server built on Express, exposing an endpoint that calls the Randomuser.me API on behalf of my frontend, collates, formats results before sending them back to my React app. On the real website, this will just pull actual data from my MongoDB store.

I get names, portrait image URLs, and use that data to mock up a testimonial page, for example.

Payload size difference between REST and GraphQL versions of the same API. This effect is amplified further in real-world projects where payload sizes can be in the MegaBytes range for each page of displayed results, and that's not even counting infinite scrolling and static assets!

I get names, portrait image URLs, and use that data to mock up a testimonial page, for example.

Step 0: Prerequisites

StepZen provides a nifty command-line tool that lets you quickly build and deploy your GraphQL API. Let's grab it with npm and install globally to make sure we can use its CLI commands.

npm install -g stepzen

Then, log in with: stepzen login.

...and enter your username, then your admin key when prompted.

Step 1: Creating the Schema

💡 The StepZen CLI offers an incredibly easy way to automatically create the schema for each REST API Endpoint you need to hit (the stepzen import command), but first, let's do it the 'normal' way because this will help familiarize you with creating GraphQL schemas in general. We'll get back to this in just a bit!

Create a new project directory, name it something that makes sense, and inside this directory, create a new file called **randomuser.graphql ** - this is your schema file. This is where we'll define our types, and our queries, specifying the endpoint for each REST API we'll need.

Our specific use case is dead simple, we'll only need to hit https://randomuser.me/api/ optionally passing in an integer (for the number of users we want to be generated) as query params. So the contents of our schema will look something like this:

// randomuser.graphql

type User {
  gender: String!
  name: JSON!
  location: JSON!
  email: String!
  login: JSON!
  dob: JSON!
  registered: JSON!
  phone: String!
  cell: String!
  id: JSON!
  picture: JSON!
  nat: String!
}

type Query {
  getRandomUser(results: Int!): [User!]!
    @rest(
      endpoint: "https://randomuser.me/api/?results=$results"
      resultroot: "results[]"
    )
}

The exclamation in GraphQL syntax indicates a non-nullable field. The braces indicate we're expecting an array of User types as output of this query.

💡 As promised, here's the easy way: automatically generate a schema, by simply importing a REST API Endpoint!

stepzen import curl <https://randomuser.me/api/?results=3> - query-name getRandomUser

This'll create some files and folders for you - most importantly, the auto-generated .graphql schema file inside the curl folder, with a query named getRandomUser that takes a number as input - exactly what we want.

And that's all, we're already done! Much simpler than writing out the schema yourself, plus you can further customize the auto-generated schema to fit your needs better.

Alternatively, you could also use StepZen's tool JSON2SDL to handle schema creation for you. Or, simply use one of StepZen's pre-built schemas from GraphQL Studio.

Finally, StepZen also needs a manifest file for any given project, and that's a file called index.graphql which will live in the same directory. It uses the special @sdl directive to load the schema from all specified GraphQL files here. As you'd expect, if you had multiple** .graphql** files, you'd include them in this array.

// index.graphql

schema @sdl(files: ["randomuser.graphql"]) {
  query: Query
}

Step 2: Deploying to the Cloud

We're done! Let's deploy!

stepzen start

This will create our GraphQL API, with our specified data sources, and - if there are no errors - deploy it to the cloud. You'll be asked to name the endpoint (the CLI provides a default that you can change), and once it's done uploading, it'll give you your endpoint URL, as well as a local proxy to your hosted GraphQL API (that you can explore with the GraphiQL GUI).

Step 3: The API Proxy Server

We're in the home stretch now. All that remains is to build our Express server at index.js that makes the queries on behalf of our frontend.

First off, dependencies.

npm i express axios dotenv

We're getting ExpressJS for our server, Axios to make HTTP requests, and dotenv to store environment variables that we don't want to expose in our source code - like our server port number, StepZen ApiKey, and our API endpoint. (If you're using a public-facing source control like GitHub, don't forget to include the .env file in your .gitignore!)

Note that while we could access any of the returned fields of the User type, for our use-case we'll only need the names and picture URLs, and so that's all we'll specify in our GraphQL query, saving us tons of bandwidth. So our GraphQL query will look something like this:

query MyQuery {
  getRandomUser(results: 3) {
    name
    picture
  }
}
// index.js

const axios = require("axios").default;
const express = require("express");
require("dotenv").config();
const app = express();

// RandomUser resource

// for routes like /randomuser?results=3 (params optional)

app.get("/randomuser", async (req, res) => {
  // GraphQL query (only names & pics)

  const body = JSON.stringify({
    query: `query MyQuery {
    getRandomUser(results:${req.query.results || 1}){
    name
    picture
    }
  }`,
  });

  // calling our deployed GraphQL API

  axios({
    url: process.env.STEPZEN_GRAPHQL_API,
    method: "post",
    headers: {
      "Content-Type": "application/json",
      "Content-length": body.length,
      Authorization: "Apikey " + process.env.STEPZEN_API_KEY,
      "User-Agent": "Node",
    },

    data: body,
  }).then((response) => {
    res.send({
      result: "success",
      users: response.data.data.getRandomUser,
    });
  });
});

// no routes match

app.use((req, res, next) => {
  const error = new Error();
  error.message = "Not found";
  error.status = 404;
  next(error);
});

// json error handler

app.use(async (error, req, res, next) => {
  res.status(404).send({
    result: "failure",
    error: error,
  });
});

// create Express.js HTTP server

app.listen(process.env.PORT || 3000);

Remember, a standard GraphQL API call should be a HTTP POST request, use the application/json content type, and include a JSON-encoded body (our query).

And we're all done! Startup our server, and then we can test it with cURL, Postman, or what have you, to see if we're getting the data we want.

// ExpressJS API response to a GET with query params ?results=3**\

{
  "result": "success",
  "users": [
    {
      "name": {
        "first": "Ivana",
        "last": "Morin",
        "title": "Madame"
      },

      "picture": {
        "large": "https://randomuser.me/api/portraits/women/29.jpg",
        "medium": "https://randomuser.me/api/portraits/med/women/29.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/women/29.jpg"
      }
    },

    {
      "name": {
        "first": "Mylan",
        "last": "Olivier",
        "title": "Mr"
      },

      "picture": {
        "large": "https://randomuser.me/api/portraits/men/25.jpg",
        "medium": "https://randomuser.me/api/portraits/med/men/25.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/men/25.jpg"
      }
    },

    {
      "name": {
        "first": "Josep",
        "last": "Gutierrez",
        "title": "Mr"
      },

      "picture": {
        "large": "https://randomuser.me/api/portraits/men/36.jpg",
        "medium": "https://randomuser.me/api/portraits/med/men/36.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/men/36.jpg"
      }
    }
  ]
}

Perfect. For generating quick-and-dirty mockups for frontend designs, this will work just fine.

The Best Of Both Worlds

GraphQL is incredibly powerful and flexible, and StepZen - with its powerful CLI tool, a plethora of features, pre-made schemas for a huge number of data sources, and extensive documentation - makes it incredibly easy to implement GraphQL and get the best of both worlds for all your development needs.

StepZen's GraphQL-as-a-service model is definitely an additional layer of abstraction, but since StepZen takes care of everything out of the box, from the perspective of you (or your org's devs), all you're doing is defining the Schema of your data, describing the API and DB calls you need and then directing client app devs to simply call the centralized StepZen GraphQL endpoint.

To sum up, StepZen makes life a whole lot simpler for both developers - whether frontend or backend - and users, since they get more data, faster.