When It’s Time to Give REST a Rest

When It’s Time to Give REST a Rest

Based upon the underlying requirement, sometimes GraphQL with Apollo Server is the best approach over using a traditional RESTful API.

Through my years of building services, the RESTful API has been my primary go-to. However, even though REST has its merits, that doesn’t mean it’s the best approach for every use case. Over the years, I’ve learned that, occasionally, there might be better alternatives for certain scenarios. Sticking with REST just because I’m passionate about it—when it’s not the right fit—only results in tech debt and a strained relationship with the product owner.

One of the biggest pain points with the RESTful approach is the need to make multiple requests to retrieve all the necessary information for a business decision.

As an example, let’s assume I want a 360-view of a customer. I would need to make the following requests:

  • GET /customers/{some_token} provides the base customer information

  • GET /addresses/{some_token} supplies a required address

  • GET /contacts/{some_token} returns the contact information

  • GET /credit/{some_token} returns key financial information

While I understand the underlying goal of REST is to keep responses laser-focused for each resource, this scenario makes for more work on the consumer side. Just to populate a user interface that helps an organization make decisions related to future business with the customer, the consumer must make multiple calls

In this article, I’ll show why GraphQL is the preferred approach over a RESTful API here, demonstrating how to deploy Apollo Server (and Apollo Explorer) to get up and running quickly with GraphQL.

I plan to build my solution with Node.js and deploy my solution to Heroku.

When to use GraphQL over REST

There are several common use cases when GraphQL is a better approach than REST:

  • When you need flexibility in how you retrieve data: You can fetch complex data from various resources but all in a single request. (I will dive down this path in this article.)

  • When the frontend team needs to evolve the UI frequently: Rapidly changing data requirements won’t require the backend to adjust endpoints and cause blockers.

  • When you want to minimize over-fetching and under-fetching: Sometimes REST requires you to hit multiple endpoints to gather all the data you need (under-fetching), or hitting a single endpoint returns way more data than you actually need (over-fetching).

  • When you’re working with complex systems and microservices: Sometimes multiple sources just need to hit a single API layer for their data. GraphQL can provide that flexibility through a single API call.

  • When you need real-time data pushed to you: GraphQL features subscriptions, which provide real-time updates. This is useful in the case of chat apps or live data feeds. (I will cover this benefit in more detail in a follow-up article.)

What is Apollo Server?

Since my skills with GraphQL aren’t polished, I decided to go with Apollo Server for this article.

Apollo Server is a GraphQL server that works with any GraphQL schema. The goal is to simplify the process of building a GraphQL API. The underlying design integrates well with frameworks such as Express or Koa. I will explore the ability to leverage subscriptions (via the graphql-ws library) for real-time data in my next article.

Where Apollo Server really shines is the Apollo Explorer, a built-in web interface that developers can use to explore and test their GraphQL APIs. The studio will be a perfect fit for me, as it allows for the easy construction of queries and the ability to view the API schema in a graphical format.

My Customer 360 Use Case

For this example, let’s assume we need the following schema to provide a 360-view of the customer:

  type Customer {
    token: String
    name: String
    sic_code: String
  }

  type Address {
    token: String
    customer_token: String
    address_line1: String
    address_line2: String
    city: String
    state: String
    postal_code: String
  }

  type Contact {
    token: String
    customer_token: String
    first_name: String
    last_name: String
    email: String
    phone: String
  }

  type Credit {
    token: String
    customer_token: String
    credit_limit: Float
    balance: Float
    credit_score: Int
  }

I plan to focus on the following GraphQL queries:

type Query {
    addresses: [Address]
    address(customer_token: String): Address
    contacts: [Contact]
    contact(customer_token: String): Contact
    customers: [Customer]
    customer(token: String): Customer
    credits: [Credit]
    credit(customer_token: String): Credit
  }

Consumers will provide the token for the Customer they wish to view. We expect to also retrieve the appropriate Address, Contact, and Credit objects. The goal is to avoid making four different API calls for all this information rather than with a single API call.

Getting Started with Apollo Server

I started by creating a new folder called graphql-server-customer on my local workstation. Then, using the Get Started section of the Apollo Server documentation, I followed steps one and two using a Typescript approach.

Next, I defined my schema and also included some static data for testing. Ordinarily, we would connect to a database, but static data will work fine for this demo.

Below is my updated index.ts file:

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = `#graphql
  type Customer {
    token: String
    name: String
    sic_code: String
  }

  type Address {
    token: String
    customer_token: String
    address_line1: String
    address_line2: String
    city: String
    state: String
    postal_code: String
  }

  type Contact {
    token: String
    customer_token: String
    first_name: String
    last_name: String
    email: String
    phone: String
  }

  type Credit {
    token: String
    customer_token: String
    credit_limit: Float
    balance: Float
    credit_score: Int
  }

  type Query {
    addresses: [Address]
    address(customer_token: String): Address
    contacts: [Contact]
    contact(customer_token: String): Contact
    customers: [Customer]
    customer(token: String): Customer
    credits: [Credit]
    credit(customer_token: String): Credit
  }
`;

const resolvers = {
    Query: {
        addresses: () => addresses,
        address: (parent, args, context) => {
            const customer_token = args.customer_token;
            return addresses.find(address => address.customer_token === customer_token);
        },
        contacts: () => contacts,
        contact: (parent, args, context) => {
            const customer_token = args.customer_token;
            return contacts.find(contact => contact.customer_token === customer_token);
        },
        customers: () => customers,
        customer: (parent, args, context) => {
            const token = args.token;
            return customers.find(customer => customer.token === token);
        },
        credits: () => credits,
        credit: (parent, args, context) => {
            const customer_token = args.customer_token;
            return credits.find(credit => credit.customer_token === customer_token);
        }
    },
};

const server = new ApolloServer({
    typeDefs,
    resolvers,
});

const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
});

console.log(`Apollo Server ready at: ${url}`);

const customers = [
    {
        token: 'customer-token-1',
        name: 'Acme Inc.',
        sic_code: '1234'
    },
    {
        token: 'customer-token-2',
        name: 'Widget Co.',
        sic_code: '5678'
    }
];

const addresses = [
    {
        token: 'address-token-1',
        customer_token: 'customer-token-1',
        address_line1: '123 Main St.',
        address_line2: '',
        city: 'Anytown',
        state: 'CA',
        postal_code: '12345'
    },
    {
        token: 'address-token-22',
        customer_token: 'customer-token-2',
        address_line1: '456 Elm St.',
        address_line2: '',
        city: 'Othertown',
        state: 'NY',
        postal_code: '67890'
    }
];

const contacts = [
    {
        token: 'contact-token-1',
        customer_token: 'customer-token-1',
        first_name: 'John',
        last_name: 'Doe',
        email: 'jdoe@example.com',
        phone: '123-456-7890'
    }
];

const credits = [
    {
        token: 'credit-token-1',
        customer_token: 'customer-token-1',
        credit_limit: 10000.00,
        balance: 2500.00,
        credit_score: 750
    }
];

With everything configured as expected, we run the following command to start the server:

$ npm start

With the Apollo server running on port 4000, I used the http://localhost:4000/ URL to access Apollo Explorer. Then I set up the following example query:

query ExampleQuery {
  addresses {
    token
  }
  contacts {
    token
  }
  customers {
    token
  }
}

This is how it looks in Apollo Explorer:

Pushing the Example Query button, I validated that the response payload aligned with the static data I provided in the index.ts:

{
  "data": {
    "addresses": [
      {
        "token": "address-token-1"
      },
      {
        "token": "address-token-22"
      }
    ],
    "contacts": [
      {
        "token": "contact-token-1"
      }
    ],
    "customers": [
      {
        "token": "customer-token-1"
      },
      {
        "token": "customer-token-2"
      }
    ]
  }
}

Before going any further in addressing my Customer 360 use case, I wanted to run this service in the cloud.

Deploying Apollo Server to Heroku

Since this article is all about doing something new, I wanted to see how hard it would be to deploy my Apollo server to Heroku.

I knew I had to address the port number differences between running locally and running somewhere in the cloud. I updated my code for starting the server as shown below:

const { url } = await startStandaloneServer(server, {
    listen: { port: Number.parseInt(process.env.PORT) || 4000 },
});

With this update, we’ll use port 4000 unless there is a PORT value specified in an environment variable.

Using Gitlab, I created a new project for these files and logged into my Heroku account using the Heroku command-line interface (CLI):

$ heroku login

You can create a new app in Heroku with either their CLI or the Heroku dashboard web UI. For this article, we’ll use the CLI:

$ heroku create jvc-graphql-server-customer

The CLI command returned the following response:

Creating ⬢ jvc-graphql-server-customer... done
https://jvc-graphql-server-customer-b62b17a2c949.herokuapp.com/ 
| https://git.heroku.com/jvc-graphql-server-customer.git

The command also added the repository used by Heroku as a remote automatically:

$ git remote
heroku
origin

By default, Apollo Server disables Apollo Explorer in production environments. For my demo, I want to leave it running on Heroku. To do this, I need to set the NODE_ENV environment variable to development. I can set that with the following CLI command:

$ heroku config:set NODE_ENV=development

The CLI command returned the following response:

Setting NODE_ENV and restarting ⬢ jvc-graphql-server-customer... done, v3
NODE_ENV: development

Now we’re in a position to deploy our code to Heroku:

$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku

A quick view of the Heroku Dashboard shows my Apollo Server running without any issues:

If you’re new to Heroku, this guide will show you how to create a new account and install the Heroku CLI.

Acceptance Criteria Met: My Customer 360 Example

With GraphQL, I can meet the acceptance criteria for my Customer 360 use case with the following query:

query CustomerData($token: String) {
  customer(token: $token) {
    name
    sic_code
    token
  },
  address(customer_token: $token) {
    token
    customer_token
    address_line1
    address_line2
    city
    state
    postal_code
  },
  contact(customer_token: $token) {
    token,
    customer_token,
    first_name,
    last_name,
    email,
    phone
  },
  credit(customer_token: $token) {
    token,
    customer_token,
    credit_limit,
    balance,
    credit_score
  }
}

All I need to do is pass in a single Customer token variable with a value of customer-token-1:

{
  "token": "customer-token-1"
}

We can retrieve all of the data using a single GraphQL API call:

{
  "data": {
    "customer": {
      "name": "Acme Inc.",
      "sic_code": "1234",
      "token": "customer-token-1"
    },
    "address": {
      "token": "address-token-1",
      "customer_token": "customer-token-1",
      "address_line1": "123 Main St.",
      "address_line2": "",
      "city": "Anytown",
      "state": "CA",
      "postal_code": "12345"
    },
    "contact": {
      "token": "contact-token-1",
      "customer_token": "customer-token-1",
      "first_name": "John",
      "last_name": "Doe",
      "email": "jdoe@example.com",
      "phone": "123-456-7890"
    },
    "credit": {
      "token": "credit-token-1",
      "customer_token": "customer-token-1",
      "credit_limit": 10000,
      "balance": 2500,
      "credit_score": 750
    }
  }
}

Below is a screenshot from Apollo Explorer running from my Heroku app:

Conclusion

I recall earlier in my career when Java and C# were competing against each other for developer adoption. Advocates on each side of the debate were ready to prove that their chosen tech was the best choice … even when it wasn’t.

In this example, we could have met my Customer 360 use case in multiple ways. Using a proven RESTful API would have worked, but it would have required multiple API calls to retrieve all of the necessary data. Using Apollo Server and GraphQL allowed me to meet my goals with a single API call.

I also love how easy it is to deploy my GraphQL server to Heroku with just a few commands in my terminal. This allows me to focus on implementation—offloading the burdens of infrastructure and running my code to a trusted third-party provider. Most importantly, this falls right in line with my personal mission statement:

“Focus your time on delivering features/functionality that extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.” – J. Vester

If you are interested in the source code for this article, it is available on GitLab.

But wait… there’s more!

In my follow-up post, we will build out our GraphQL server further, to implement authentication and real-time data retrieval with subscriptions.

Have a really great day!