When It’s Time to Give REST a Rest
Tags: graphql, heroku, restapi, webdev
Published: 2024-05-11 15:34:52

![Article Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g50tprjqmnyangvqch51.jpg)

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](https://graphql.org/) 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](https://www.apollographql.com/docs/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](https://www.npmjs.com/package/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:

```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
  }
```

I plan to focus on the following GraphQL queries:

```graphql
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](https://www.apollographql.com/docs/apollo-server/getting-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:

```graphql
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:

```plaintext
$ npm start
```

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

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

This is how it looks in Apollo Explorer:

![Image #1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ycgr2stg6ze2nuomk84.png)

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

```json
{
  "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:

```typescript
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):

```plaintext
$ 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:

```plaintext
$ heroku create jvc-graphql-server-customer
```

The CLI command returned the following response:

```plaintext
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:

```plaintext
$ 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:

```plaintext
$ heroku config:set NODE_ENV=development
```

The CLI command returned the following response:

```plaintext
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:

```plaintext
$ 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:

![Image #2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9hzkdw6zl9yv2cxf07m.png)

If you’re new to Heroku, [this guide will show](https://www.apollographql.com/docs/apollo-server/deployment/heroku/) 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:

```typescript
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`:

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

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

```json
{
  "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:

![Image #3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w5leciaqt570g3jk8bf9.png)

## **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](https://gitlab.com/johnjvester/graphql-server-customer).

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!