Watch a demoFree trial
Blog
Blog

GraphQL vs REST: A practical guide to modern APIs

APIdeveloper workflowapplication modernization
19 August 2025
Share

REST APIs can box you in, forcing you to jump through hoops for the data you want. GraphQL flips that around. You ask for what you need, and that’s all you get without wasted payloads or filler.

If you’ve tried wiring up an app with lots of connected data, you’ve probably felt REST’s limitations. Sometimes you end up digging through a pile of data you didn’t ask for. Other times, the one thing you need just isn’t there. Either way, your users feel the lag.

Picture your storefront app. All you want are customer names and their latest orders. If you’re using REST, you’ll end up making a bunch of separate requests just to piece things together. With GraphQL, you fire off one query to a single endpoint and get everything you need in one go.

REST example:

  1. Call/customer: Retrieve all customer data, including unnecessary fields like email and age.
  2. Call/orders: Get all orders for that customer, filtering needed details manually.

The REST calls might look like this:

GET /customer/1

{
  "id": 1,
  "name": "Alice",
  "email": "alice@example.com",
  "age": 30
}

GET /orders?customerId=1

[
  { "id": 1, "title": "Laptop" },
  { "id": 2, "title": "Mouse" }
]


 This REST approach sends excessive data, requiring extra work to filter through it.

GraphQL example:

GraphQL handles this more efficiently with a single focused query:

query {
  customer(id: 1) {
    name
    orders {
      title
    }
  }
}

Result:

{
  "customer": {
    "name": "Alice",
    "orders": [
      { "title": "Laptop" },
      { "title": "Mouse" }
    ]
  }
}


The single request delivers exactly what the frontend needs. No extra roundtrips, no redundant data, simply direct and efficient.

The evolution of GraphQL: From Facebook to API standard

Facebook built GraphQL in 2015 to get data quickly to the News Feed. After making it open source, other companies started using it because it let them handle data requests through a single endpoint with more precision.

Here's what makes GraphQL stand out:

  • It lets you request exactly what you need - no extra data
  • Your frontend code works better with the GraphQL schema
  • It streamlines data relationships between multiple data sources

If you're working with connected data structures, GraphQL's query system makes development straightforward.

GraphQL: One endpoint for all your data needs

What sets GraphQL apart

GraphQL is not just another API - it's a query language that lets you get exactly the data you need through a single endpoint. While this makes client-server communication simpler, you'll want to understand what it brings to the table.

Unlike REST APIs, where you'd need multiple calls for different data, GraphQL brings it all together in one query. Just keep in mind you'll need to think about caching and performance as you build.

Let's look at how GraphQL shapes real applications and why developers are choosing it for modern API development.

Core GraphQL concepts

GraphQL handles data in some unique ways that matter for developers.

  • Precise data fetching: Request exactly what you need and nothing more. GraphQL eliminates over-fetching, but you'll need to manage query complexity.
  • Type safety built in: Schema validation catches issues early. Design your schema carefully.
  • Client-driven data: Clients request exactly what they need, but require solid caching for performance.

GraphQL gives you a flexible and precise API layer. With thoughtful implementation of caching, performance, and schema design, it can make your development process more efficient.

Now let's see how GraphQL uses schemas and types to balance flexibility with performance.

Let's talk GraphQL schemas and types: what you need to know

What a GraphQL schema is

A GraphQL schema defines what data your API can work with - think of it as a contract between your client and server. Using a simple language called SDL (Schema Definition Language), it sets clear boundaries for data exchange.

Let's see this in action with a basic "User" type:

type User {
  id: ID!
  name: String!
  email: String!
}

type Query {
  getUser(id: ID!): User
}

 

The schema acts like a clear set of rules for data requests. You tell GraphQL what fields you want, and the schema checks if those fields exist. Like a menu where you can pick exactly what you need.

Schemas help organize data connections in a way that makes your queries more efficient, giving you just the specific information you're looking for.

Building blocks of GraphQL data relationships

Your schema also shows how different parts of your data link together. GraphQL lets you query these connections through a single endpoint, making it simpler than using multiple REST endpoints.

type Post {
  id: ID!
  title: String!
  content: String!
  author: User
}

 

Each post can link to its author. Unlike REST APIs that need multiple API calls, GraphQL queries can fetch both post and author data at once through a single endpoint - it's a streamlined query language for APIs.

Best practices for schema design

Your schema needs to be both flexible and maintainable if you want it to grow with your app. Here's what works:

  • Use granular types: Split larger objects into smaller, focused types. This lets you query exactly what you need from your API.
  • Leverage enums and input types: Use enums when you know all possible values upfront, and input types to keep your mutations organized and clear. Since we’ve walked through the fundamentals, I’ll show you a code example so you can see how this plays out in real life.
enum Role {
  ADMIN
  USER
}

input UpdateUserInput {
  id: ID!
  name: String
  email: String
}

 

  • Name with clarity: Use simple, descriptive names in your schema that clearly show what each piece does.

A well-structured schema makes it easier for clients and servers to communicate clearly through a single endpoint.

Let's see how these schema concepts work in practice with GraphQL.

Core operations

Working with Data in GraphQL

GraphQL gives you two simple tools to handle data: queries and mutations. These make it easy to work with your API.

  • Queries: Think of these as your data requests. They let you ask for exactly what you want, making your app faster by avoiding unnecessary data.
query {
  getUser(id: "1") {
    id
    name
    email
  }
}

 

  • Mutations: You can change data on your server through a single endpoint. It's simpler than REST methods like POST, PUT, and DELETE, but you'll need to set up resolver functions carefully to handle different data changes. The type checking built into the interface finds errors during development.
mutation {
  updateUser(input: { id: "1", name: "John Doe" }) {
    name
    email
  }
}

 

GraphQL keeps things simple with two main ways to work with data: queries to get information and mutations to change it. Everything happens through one connection point, making your code clean and easy to follow.

Real-time updates with subscriptions

GraphQL works with real-time updates through subscriptions that need WebSocket connections and server setup. Once it's running, your app gets updates right when they happen.

Here's what subscriptions look like in a chat app:

subscription {
  onNewMessage(roomId: "123") {
    content
    timestamp
  }
}


When something new happens (like a new chat message), subscriptions send the updates to connected clients through WebSocket connections. You won't need to keep checking for updates, but you'll need specific server setup and WebSocket support.

Error handling in GraphQL vs REST

GraphQL and REST take different paths when it comes to errors. In REST, you get an HTTP status code and an error message in the response body, so you know what failed. GraphQL keeps it simple. Unless there’s a network issue, you’ll always get a 200 OK, and any errors show up in a dedicated errors field in the response.

Have a look:

{
  "errors": [
    {
      "message": "User not found",
      "path": ["getUser"]
    }
  ]
}

 

GraphQL provides clear error messages when something goes wrong. The schema validation helps developers catch and fix issues during development rather than in production.

Like a helpful translator, GraphQL simplifies communication between your app and data. It packages everything you need into a single request and keeps information flowing smoothly.

Let's see how to get GraphQL running in production with Apollo Server, a solid tool for building GraphQL APIs that many developers trust.

Setting up GraphQL in production

Getting started with Apollo

You can build a GraphQL API that handles all your data through one endpoint with the Apollo server. It's straightforward to get started.

  1. Install dependencies:
npm install apollo-server graphql

 

Create a basic server:

Here’s an example of creating an Apollo server with a sample schema and resolver.

const { ApolloServer, gql } = require('apollo-server');

// Define the GraphQL schema using `gql`
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// Define the resolvers for the schema
const resolvers = {
  Query: {
    hello: () => 'Hello, world!',
  },
};

// Create a new Apollo Server instance
const server = new ApolloServer({ typeDefs, resolvers });

// Start the server and log the URL
server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

 

To secure your GraphQL API, add a JWT validation step to your middleware layer. Here's how:

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const token = req.headers.authorization || '';
    const user = validateJWT(token); // Custom function to decode/validate JWT
    return { user };
  },
});

 

This security setup means only users who've logged in can use your GraphQL API. You'll also need field and type-level permissions set up - this lets users access and change only the data they're meant to see, all while keeping your single endpoint efficient.

Next, let’s look at how to connect GraphQL with modern client apps using Apollo Client - a tool that makes frontend data management simpler.

Apollo Client integration

Let's set up Apollo Client - it's a GraphQL client that makes your API connections simple. You can use it to link your frontend with a GraphQL API through a single endpoint, so you're only getting the data you need.

  1. Install client dependencies:
npm install @apollo/client graphql

 

Initialize Apollo Client:

Setting up Apollo Client with a GraphQL endpoint and in-memory caching:

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://example.com/graphql',
  cache: new InMemoryCache(),
});

 

  1. Enable global access to query your GraphQL API via a single endpoint:

Wrapping your React app with the ApolloProvider makes data queries easy across your entire application:

import { ApolloProvider } from '@apollo/client';

import App from './App';

const Root = () => (
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);

 

With Apollo Client ready, let's get our API secure and make sure users can access only the data they should see.

Authentication and authorization with a single API

Here's how role-based access control with JWT tokens works in resolvers. Here's a practical example showing how API calls get authorized:

const resolvers = {
  Query: {
    getUser: async (_, { id }, { user }) => {
      if (!user || user.role !== 'ADMIN') {
        throw new Error('Unauthorized');
      }
      return await User.findById(id);
    },
  },
};

 

Apollo Server takes your API data from the JWT token and makes it available in your GraphQL resolvers. This way, you can use that data to check if someone has permission to query or mutate the data source.

Testing setup

Testing helps ensure your API is working correctly. We'll use Jest for our tests since it's straightforward and effective.

Write a test for a query:

describe('GraphQL Queries', () => {
  it('fetches user data', async () => {
    const query = `
      query {
        getUser(id: "1") {
          id
          name
        }
      }
    `;
    const response = await executeGraphQL(query); // Replace with test setup logic
    expect(response.data.getUser.name).toBe('John Doe');
  });
});


Catch issues early by testing often. Your schema will stay reliable, and you'll avoid debugging issues. With testing covered, let's look at optimizing your GraphQL API performance.

Performance optimization

Making GraphQL faster with DataLoader

GraphQL APIs often run into the n+1 query problem. This happens when an API request gets one piece of data, then needs related data - creating multiple database calls that slow things down.

DataLoader makes GraphQL faster by combining database calls and storing results. Here's how GraphQL optimizes API calls to get exactly the data you need:

const userLoader = new DataLoader(keys =>
User.findMany({ where: { id: keys } })
);
// Resolver example
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId),
},
};

 

DataLoader combines database queries, reducing server load and speeding up API responses. It makes managing APIs straightforward.

Next, let's see how caching can make your GraphQL API even faster.

Caching in GraphQL Applications

GraphQL doesn't come with built-in caching, but the tools in its ecosystem give you solid caching options. Here's what you can do to add caching to your GraphQL apps:

  • Client-side caching: Apollo Client stores your query results locally, so you'll get faster responses and snappier UI updates when you set it up right.
  • Server-side optimization: Apollo Server lets you cache responses, which cuts down on database load and speeds up responses for data that's requested often.

Let's look at how to set up caching with Apollo Client:

import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
import { ApolloClient, InMemoryCache } from '@apollo/client';

// Configure Apollo Client with persisted queries
const client = new ApolloClient({
  link: createPersistedQueryLink().concat(httpLink),
  cache: new InMemoryCache(),
});

 

Keep an eye on performance

Monitor your GraphQL API to ensure it stays fast and reliable. Apollo's tools show you which queries are popular and where slowdowns happen.

When queries get slow, you'll know right away where to add caching or use DataLoader to speed things up. Regular monitoring helps catch and fix issues early.

Running GraphQL with Upsun

You'll find deploying with Upsun straightforward and fast. We've got built-in caching and deployment tools that let you run your API in production right away. Testing and production environments are quick to set up, so you can start working immediately.

If you've got APIs that need to work in different regions, our global caching keeps things fast and steady. That means your users don't have to wait around—they'll get quick responses no matter where they are, and your GraphQL service stays snappy as you grow.

Use cases

Mobile apps: quick and efficient

GraphQL lets mobile apps get data with a single API call. You'll request exactly what your app needs, just usernames rather than complete profiles.

Consider a mobile app displaying profiles, activities, and notifications. While REST APIs might require multiple calls, GraphQL can consolidate these into one request, which may improve battery life and response times based on your specific implementation and caching strategy.

Connected data: streamlined queries

GraphQL excels at handling linked data, letting you get all related information in one query.

Picture you’re looking up a customer’s order history and want details on what they bought, who sold it, and where each package is right now. With GraphQL, you just ask for everything in one go. If you’re working with REST, you’d be bouncing between multiple endpoints and piecing the info together on your own.

If you’re running microservices, GraphQL can act as your central API gateway. Tools like schema stitching or Apollo Federation let you pull all your services under one roof.

Picture this: users, orders, and products are split into their microservices. GraphQL ties them together with a single schema. The gateway figures out where to find each service, handles the requests, and merges the data for you.

Once you see this approach in action, it’s easier to compare how GraphQL and REST each deal with distributed systems.

GraphQL vs REST

Data retrieval patterns

REST APIs give you fixed endpoints for each resource, but that isn't always the best fit. Without good planning, you'll either get more data than you need (like getting full user profiles when you only want names) or you'll need to make multiple calls to get related data. Modern REST APIs can handle this better by letting you pick specific fields and using other methods to keep things efficient.

GraphQL lets you be precise. One endpoint, exact data. Want a user's name and recent posts? That's what you get.

Example REST response (overfetching):

{
"id": "1",
"name": "John Doe",
"birthdate": "1990-01-01",
"address": { "street": "123 Main St", "city": "Example City" }
}
Example GraphQL query (specific data fetching):
query {
getUser(id: "1") {
name
posts {
title
}
}
}

 

Now let's check out how REST and GraphQL handle data in real situations.

Performance differences

REST works well for simple data that maps to endpoints. But when you need nested data (like users and their posts), you'll need multiple calls or get back extra data you don't need.

GraphQL shines with linked data when you set it up right. You'll write one query to get exactly what you need, which can make your app run faster when it's set up properly. But remember, how fast it runs depends on your caching setup and how you build it. Don't forget to set up DataLoader on your server to prevent N+1 query slowdowns.

Version management

REST and GraphQL don’t handle updates the same way. In REST, it’s common to version your API right in the URL, think /v1/users. Modern REST can also use content negotiation and hypermedia to update without version numbers.

GraphQL lets you update gradually by marking fields as deprecated. Your code keeps working while giving developers time to adapt their code.

Here's how to mark fields as deprecated in your schema:

type User {
  id: ID!
  name: String!
  email: String @deprecated(reason: "Use 'contactEmail' instead.")
  contactEmail: String
}

 

GraphQL lets you update your API gradually. You don't need to push everyone to update at once - devs can shift their code when it works for them.

Benefits and challenges

What makes GraphQL great for developers?

GraphQL comes with built-in tools that let you explore APIs easily during development. While you'll want to turn off tools like GraphiQL and GraphQL Playground in production for security, the type system lets you catch errors early. This type checking works with schema validation to spot problems quickly and makes it simpler for frontend and backend teams to work together.

How does GraphQL address overfetching and underfetching?

REST APIs often give you too much data (overfetching) or require multiple endpoint calls (underfetching). Neither is ideal.

GraphQL lets you request precisely what you need in one query. Want to grab a user's name and their latest posts? That's one query instead of multiple REST endpoints. When properly implemented, you can get smaller payloads and reduced network traffic. Development workflows might be simplified for certain types of applications, especially those with complex data relationships.

Let's look at how caching can make these quick queries run even faster.

Let's talk about caching in GraphQL APIs

Here's what you need to know about making your GraphQL apps run faster with caching.

  • Frontend caching: When you use Apollo Client, it keeps a copy of your query results right on the device. Your app loads data more quickly, and you don’t have to hit the server as often. You'll want to pick the right cache settings (cache-first or network-only) to keep your data in sync.
  • Backend caching: GraphQL typically doesn't work with CDN caching since it uses POST requests. But don't worry - you can use Automatic Persisted Queries (APQ) to turn queries into cacheable GET requests with unique hashes.

Caching isn’t a one-size-fits-all deal. REST APIs play nicely with HTTP caching and CDNs right out of the box, but with GraphQL, you’ll have to do a bit more work to get the same results. It’s worth stepping back and considering what your app really needs before you settle on a caching approach.

Keep your GraphQL API secure

Let's build security into your GraphQL API from the start. With the right protection, you won't run into issues with complex nested queries slowing down your server. Here's what you need:

  • Set limits on query complexity and depth to prevent slow performance from resource-heavy operations
  • Add rate limiting to protect your API and maintain stable service

You'll want to use graphql-shield on your server for authorization rules. With this middleware, you set up the rules for who can see or change certain data in your GraphQL schema. That way, users only get access to what they’re supposed to.

Quick tips for GraphQL

GraphQL gives you a flexible query language for APIs. While GraphQL and REST can both get data efficiently, GraphQL's schema-first design and built-in type system work really well for certain tasks. It's got solid tools for type checking and real-time updates, which makes it great for modern apps that need lots of data.

  • Start simple: Design your GraphQL schema to match what your app needs to do
  • Pick basic tools: Use Apollo GraphQL to get started without complex setup
  • Test and monitor your GraphQL API calls to keep performance strong

New to GraphQL? Start by using it for just one feature in your app. This lets you learn the basics while keeping your current systems running.

Try GraphQL with small implementations first. This lets you see how it fits what your team needs to build.

Start building with GraphQL

Want to start building with GraphQL? Let's look at how you can begin.

  • Begin with the basics at GraphQL.org and Apollo GraphQL documentation to understand the core concepts.
  • Practice with real code: Spin up a tiny project with Apollo Server and Client. Try out some real queries and mutations to see how things work in practice.

When you're ready for production, use infrastructure tools like Upsun that offer environment cloning and caching mechanisms to keep your deployment smooth.

Start small, pick a feature to try with GraphQL, test it thoroughly, and build from there. That's the straightforward path to creating clean, efficient APIs.

Your greatest work
is just on the horizon

Free trial
Discord
© 2025 Platform.sh. All rights reserved.