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.
Join the DZone community and get the full member experience.
Join For FreeThrough 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 informationGET /addresses/{some_token}
supplies a required addressGET /contacts/{some_token}
returns the contact informationGET /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!
Opinions expressed by DZone contributors are their own.
Comments