GraphQL vs REST – How to Choose the Best Option for Your Project?

GraphQL vs REST – How to Choose the Best Option for Your Project?

The growing popularity of GraphQL has led to many theories that often don’t have much in common with the truth. One of them says that this language will replace REST. In fact, it’s more of a complementary tool than a rival.

You can use both technologies for mobile and web app development. In this article, I compare the two options, mainly focusing on GraphQL and its role in the project. I also explain when it’s best to use this language.

What do you need to know about the data communication and REST API?

Application Interface Programming (API) is used to communicate between two programs or devices. It’s a set of rules and protocols describing how the information exchange should be conducted.

A standard way to communicate with the WWW server is the HTTP (Hypertext Transfer Protocol). It defines:

  • Request methods
  • Headers
  • Statuses
  • Response body
  • Cookies
  • Authorization mechanism.

REST (REpresentational State Transfer) defines a set of principles we need to follow to make the web service scalable and optimize interactions between the client and server. Such requirements include a uniform interface, statelessness, and client-server decoupling. If they are all met, this API is called RESTful.

The core concept in any RESTful API is the resource. Every resource has an address, which is called an endpoint.

Two problems come from the resource concept and the fact that you can only send a single request via an HTTP connection.

  • Over-fetching

It forces the client to retrieve the data they don’t need. As a result, sending requests takes more time.

  • Under-fetching

It occurs when one endpoint doesn’t provide the data the client requires. It forces them to send requests on several endpoints that can also return unnecessary data.

The solution to these two problems is creating new endpoints adjusted to specific requirements. Unfortunately, in the case of complex systems, it can become a never-ending task.

Introduction to GraphQL

GraphQL is a data query language for API. It changes how apps fetch data and enables obtaining required info with a single query. As a result, there’s no need to struggle with the responses from various REST endpoints.

Thanks to a strongly typed schema, GraphQL helps us define relations between data in any number of systems. It allows us to focus on the purpose of this data instead of wondering where it’s stored.

How does the GraphQL work?

Let’s see what happens when the GraphQL server receives an HTTP request (also called an operation):

  1. (Query Parsing)
    First, the server extracts a string with the GraphQL operation, analyzes it, and transforms it into a tree-structured document called AST (Abstract Syntax Tree). It makes it easier to manipulate it.
  2. (Validation Against Schema)
    The server, using AST, verifies the accuracy of operations regarding types and fields in the schema. Suppose it detects any irregularities (for example, a required field isn’t defined in a schema, or the operation is formulated incorrectly). In that case, it returns an error and sends it back to the app.
  3. (Execute Resolver)
    Based on schema, the server adjusts the resolver function to every field in the operation. The mission of this function is to resolve the field by assigning it an appropriate value. It is usually based on data from sources such as a database or REST API. These sources don’t have to be located inside the GraphQL server – external sources are also acceptable.
  4. (Response)
    When all operation fields are resolved, data is turned into an organized JSON object with precisely the same structure as the query. It is located in a data field; consequently, all the operation errors are in the errors field.

The above process can be slightly different, depending on the implementation of GraphQL specification. It only describes technical requirements but doesn’t say, for example, what statuses we should adopt in the case when GraphQL is served over HTTP.

Before I move on to examples using GraphQL, I will introduce some basic terms you should know to use this language.

What is schema?

It’s a critical element of GraphQL. A schema is a contract between a server and a client that defines what the latter can or cannot do. It tells us what operations are available and what response types we can expect. We write schema with SDL (Schema Definition Language).

The syntax of this language consists of several elements:

● Scalar types are the base types that represent a single value. They are building blocks of complex types. GraphQL provides several built-in options: Int, Float, String, Boolean, and ID. There is a possibility to create your own units.

● Object types – complex types, built from fields. Each field has its own type. Query, Mutation, and Subscription are special types, called root types. All available operations come from them. You can change their names if you like.

● Interfaces – abstract types that enable defining common fields for different types of objects. These objects that implement the interface must contain all its defined fields.

● Union types are special types that enable returning one of a few types of objects. They are handy when you want to return objects of various types without shared fields.

● Enums are special types that represent predefined lists of available values.

● Input types – special types that transfer complex objects as arguments to operations.

I chose a schema-first approach in this article, meaning schema is created manually before implementation. A code-first approach is also possible. In such cases, schema is generated based on code. Both approaches have their pros and cons. I won’t focus on them here, though.

Types of operations

Operations are requests that the client sends to the GraphQL server. In them, the client specifies what data it wants to receive.

When do we perform specific operations?

Query – we choose this operation when we want to retrieve the data.

Mutation – when we add or update data.

Subscription – when we want to update data in real-time. To handle this operation on the server side, we rarely use the HTTP protocol. Usually, we choose websocket. Subscription is a broad topic that deserves a whole new article so I won’t delve into details here.

Resolver – a function called on a server that resolves the field to which it is assigned. A corresponding resolver must exist for every data type defined in a schema.

In the example above, I defined a map of resolvers with the addTodo function, which adds a task to a to-do list. This function must have the same name as a type defined in a schema to make a resolver work correctly. Moreover, the map of resolvers must be transferred during the initiation of the GraphQL server.

App example with REST API and GraphQL

To explain the difference between these two approaches, I wrote a simple backend app. First with REST API and then GraphQL.

This app has three features:

  1. Uploading a single task after giving its ID.
  2. Uploading all created tasks.
  3. Creating a new task.

I’m not going to connect with a database. Instead, I will store created tasks in an array in code. It will simplify the whole process. Moreover, I will also use some other solutions to minimize the risk of unnecessary distractors coming up.

REST API approach

Below, I show you how to implement this app with the REST approach using JSON as a data format.

import express from "express";
import crypto from "crypto";
import bodyParser from "body-parser";

function generateId() {
  return crypto.randomBytes(16).toString("hex");
}

const PORT = 4000;
const app = express();

app.use(bodyParser.json());

const todos = [];

app.get("/todos/:id", (req, res) => {
  const todo = todos.find((t) => t.id === req.params.id);
  if (!todo) {
    res.status(404).send("Todo not found");
  } else {
    res.json(todo);
  }
});

app.get("/todos", (req, res) => {
  res.json(todos);
});

app.post("/todos", (req, res) => {
  const todo = {
    id: generateId(),
    task: req.body.task,
    status: "TODO",
  };
  todos.push(todo);
  res.status(201).json(todo);
});

app.listen(PORT, () => {
  console.log(`Example app listening on port ${PORT}`);
});

What should catch your attention here? Methods (get and post) triggered on the app object. They defined the names of endpoints and reactions to their triggering.

​​Server communication – JavaScript example

In JavaScript, the simplest way is to retrieve data with the in-built fetch method.

fetch("http://localhost:4000/todos");

Remember that fetch has a GET method set up by default.

When transferring the message, you can use the same method. And when posting the message, you can use the same built-in function. But you have to pass a specific option object, which changes HTTP behavior.

const newTodo = { task: "Task 1" };
fetch("http://localhost:4000/todos", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(newTodo),
});

GraphQL approach

Now, I will show you what the same app would look like with GraphQL.

Like in the example before, I store tasks in the asset in the code and use JSON data format.

import express from "express";
import { graphqlHTTP } from "express-graphql";
import { buildSchema, GraphQLError } from "graphql";
import crypto from "crypto";

function generateId() {
  return crypto.randomBytes(16).toString("hex");
}

const schema = buildSchema(`
  type Query {
    todo(id: ID!): Todo
    todos: [Todo!]!
  }

  type Mutation {
    addTodo(task: String!): Todo
  }

  type Todo {
    id: ID!
    task: String!
    status: TodoStatus!
  }

  enum TodoStatus {
    TODO
    DONE
    IN_PROGRESS
  }
`);

const todos = [];

const resolvers = {
  todo: ({ id }) => {
    const todo = todos.find((todo) => todo.id === id);
    if (!todo) {
      return new GraphQLError("Todo not found");
    }
    return todo;
  },
  todos: () => {
    return todos;
  },
addTodo: ({ task }) => {
    const todo = {
      id: generateId(),
      task,
      status: "TODO",
    };
    todos.push(todo);
    return todo;
  },
};

const PORT = 5000;
const app = express();

app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: resolvers,
    graphiql: true,
  })
);

app.listen(PORT, () => {
  console.log(`Example app listening on port ${PORT}`);
});

When implementing a GraphQL server, we don’t define an endpoint for every possible operation (unlike the REST approach). Instead, we need to define schema and resolvers. Then, we pass them as arguments to the graphqlHTTP function that serves as a middleware for all requests directed to the /graphql path (it’s a generally accepted name, but you can change it if you like).

Additionally, the express-graphql implementation enables us to turn on graphiQL, which allows interactive tests of available operations.

In a production code, we usually use tools and techniques that help us develop GraphQL server. For example, the schema is often located in a separate file (e.g., schema.graphql) so the code editor can highlight the syntax and detect bugs.

Server communication – JavaScript example

Just like with REST, in GraphQL, the easiest way to send query to API with JavaScript is to use a built-in fetch method.

A difference lies in defining a query (in this case, getTodos) that we write with a special query language, GraphQL. It’s different from the language we use to design the schema. It resembles JSON in many ways.

const getTodos = `
  query {
    todos {
      id
      task
      status
    }
  }
`;

fetch("http://localhost:5000/graphql", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query: getTodos }),
});

A mutation is conducted analogically to the query.

const addTodo = `
  mutation AddTodo($task: String!) {
    addTodo(task: $task) {
      id
      task
      status
    }
  }
`;

const variables = {
  task: "Task 1",
};

fetch("http://localhost:5000/graphql", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    query: addTodo,
    variables,
  }),
});

In the example above, I also give variables. You can embed arguments required in the options within it.

To make using GraphQL API more accessible, we usually take advantage of ready-to-use clients (tools for making queries), such as Apollo Client.

Testing requests with GraphiQL

Most implementations (if not all) have built-in interactive environments to test GraphQL queries directly from the browser.

In the case of express-graphql, you can choose, for example, GraphiQL. It’s a good starting point for creating an operation for the GraphQL API or getting familiar with its documentation.

GraphiQL isn’t the only available option. For instance, GraphQL Playground and Apollo Sandbox are more extended alternatives.

GraphiQL example

Benefits of using GraphQL

GraphQL offers many advantages, such as:

● Effective data fetching – it allows clients to define what data they need. It eliminates the problem of over-fetched data that REST API often provides.

● Strong typing – every GraphQL response is predictable because every field and data type is defined in a schema.

● API introspection – GraphQL schema can be fetched continuously, so we can quickly check what queries are allowed.

● Queries for many data sources – GraphQL makes creating queries to many data sources simultaneously possible. It facilitates data aggregation from many locations.

● Event handling in real time using subscriptions  – thanks to this, updates on the client side take place only when data changes.

● Evolvability – GraphQL enables us to add new fields and types to API without impacting the existing queries. Older fields can be tagged as outdated and hidden from the introspection tools.

● Support for different platforms – there are many libraries for different languages and platforms that facilitate work with GraphQL, both on the server and the client side.

GraphQL vs RESTful – which is better?

There is no clear answer to this question. GraphQL has the potential to become a better solution when it comes to app scalability on many platforms. It is also more efficient.

On the other hand, it’s easier to make mistakes with it, especially when you don’t have much experience. The simplest example is the lack of use of security features that protect from multiple nestings:

In the example above, a logged-in user has friends who have friends, etc.

If I don’t add any mechanism to limit the nestings, I’d make a potential wicket for a DDoS attack with a single query.

Lucky for us, there are tools for these types of problems. They can effectively limit the potential abuse of API. But without certain knowledge, we can easily become the target of the attack.

Compared to REST, GraphQL doesn’t have such a developed community and tools. It can make finding answers to specific problems more difficult.

Remember that GraphQL isn’t the enemy of REST API. It should be regarded as a supporting tool. So, nothing stops you from choosing REST first, and then, if necessary, you can use a website created this way as a data source and GraphQL as an abstraction layer serving as an interface.

So, if you need help deciding what solutions to choose for a start, I suggest REST. Why? It’s simpler, and more people understand how it works. If needed, you can connect it with GraphQL later. Although, it won’t always be the right choice.

When is it best to start with GraphQL?

In two exceptional situations, it’s good to consider GraphQL as the entry approach:

  1. When many teams work on the app (e.g., in the case of cross-platform projects). GraphQL allows you to define a schema and minimize the time needed for communication.
  2. When the project significantly relies on real-time data. Thanks to subscriptions, we can simplify the communication.

Remember to consider the team’s experience and problems specific to your project. It’s best to make sure that GraphQL offers solutions to them.

Useful links 

In this article, I didn’t mention that we use various supporting tools when implementing GraphQL. If you’re interested in this topic, check out the Apollo platform. And if you use a TypeScript language, see the The Guild tools.

Fabian - Web developer

Fabian Kuriata

Web App Developer

Learn more

Project estimation

Let us know what product you want to build and how we can help you.

Why choose us?

Logo Mobile Trends Awards

Mobile Trends Awards 2021

Winning app in
EVERYDAY LIFE

Nagroda Legalnych Bukmacherów

Legal Bookmakers Award 2019

Best Mobile App

Mobile Trends Awards logo

Mobile Trends Awards 2023

Winning app in MCOMMERCE DEVELOPMENT

23

client reviews

Clutch logo