Skip to main content

Command Palette

Search for a command to run...

Your API doesn't need GraphQL

80% of apps don’t need it.

Updated
•5 min read•View as Markdown
Your API doesn't need GraphQL

Imagine setting up GraphQL on projects that had 7 endpoints. You spend days configuring resolvers, fighting N+1 queries, debugging why errors came back as 200s, only to realise REST would have shipped the same thing faster.

GraphQL is powerful. But most apps don't need what it offers.


The Shift From REST to GraphQL

GraphQL was built at Facebook to solve real scaling problems. Too many API calls, too much unnecessary data, multiple clients with different needs - it made sense.

But then the industry started using it everywhere.

Suddenly, even small apps with a single frontend and a few endpoints were adding schemas, resolvers, DataLoaders, frontend GraphQL clients like Apollo and query layers because GraphQL felt like the "modern" architecture choice.


So What is GraphQL, Actually?

GraphQL is a query language for your API. Instead of hitting multiple endpoints and getting back whatever the server decides, the client sends a single query describing exactly what data it needs and gets back only those fields.

GraphQL usually runs through a single endpoint like /graphql, where queries, mutations, and subscriptions are handled.


How GraphQL Actually Works

The Schema

Everything in GraphQL starts with the schema. It acts like a contract between the client and server, defining what data exists and what can be queried.

type User {
  id: ID!
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
}

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

This becomes the source of truth for your API. Clients can clearly see what fields are available, which ones are required, and what type of data they’ll get back.

query {
  user(id: "1") {
    name
    posts {
      title
    }
  }
}

Resolvers — How GraphQL Fetches Data

When a query comes in, GraphQL calls resolver functions for each requested field. Resolvers act as the bridge between the GraphQL layer and your application logic.

const resolvers = {
  Query: {
    user: (parent, args, context) => {
      return userService.getUserById(args.id);
    }
  },

  User: {
    posts: (parent, args, context) => {
      return postService.getPostsByUserId(parent.id);
    }
  }
}
  • Query.user fetches the user using args.id

  • The returned user object becomes the parent for the next resolver

  • User.posts uses parent.id to fetch posts for that user

  • GraphQL combines everything into a single response automatically

The Request Path

Here's what actually happens when a GraphQL query hits your server:

GraphQL walks through the query field by field, calls the matching resolvers, and assembles the final response shape automatically.

The N+1 Problem

Let's take an example query:

query {
  posts {
    title
    author {
      name
    }
  }
}

Here’s what can happen behind the scenes:

  1. GraphQL first fetches all posts - 1 database query

  2. Then for every post, it separately fetches the author - N additional queries

So if there are 10 posts, the server ends up making:

1 query for posts + 10 queries for authors = 11 database calls

That’s called the N+1 problem.

To avoid this, GraphQL applications usually use tools like DataLoader to batch and cache repeated requests so authors can be fetched together instead of one by one.

Post: {
  author: (parent, args, context) => {
    return context.userLoader.load(parent.authorId);
  }
}

Instead of fetching each author separately, DataLoader collects all the authorIds first and then it batches them into a single database query:

SELECT * FROM users WHERE id IN (1,2,3,4,5)

So instead of many small queries, it batches them into one optimized fetch.


What GraphQL Actually Solves

Getting only the data you need - Sometimes a frontend only needs 2–3 fields, but a REST endpoint returns a huge object with lots of unnecessary data. GraphQL lets the client request only the required fields.

Supporting different clients easily - A mobile app, web app, and admin dashboard may all need different data from the same backend. With GraphQL, each client can request its own data shape without creating separate endpoints.

Helping fast-changing frontends - If the UI changes often, frontend teams can update queries themselves without constantly asking backend teams to modify APIs.

**Handling connected data better -**GraphQL works well when data is deeply linked like users, posts, comments, tags, followers, dashboards, etc.


What REST Still Does Better

Caching is simpler - REST works naturally with browser and CDN caching using normal GET requests. GraphQL usually needs extra setup for proper caching.

Errors are easier to understand. In REST:

  • 404 means not found

  • 403 means unauthorized

  • 500 means server error

In GraphQL, many responses still return 200 OK, even when errors happen, which can make debugging harder.

File uploads are easier - REST handles file uploads cleanly using standard HTTP APIs. In GraphQL, uploads often need additional libraries or separate REST endpoints anyway.

It’s easier to learn and maintain - Most developers already understand REST. GraphQL adds extra concepts like schemas, resolvers, DataLoaders, Apollo client, and query management.


The Right Tool for the Right Problem

The best systems usually don’t treat REST and GraphQL as enemies. They use each where it makes sense.

  • REST for auth (/login, /refresh, /logout), file uploads, webhooks, public-facing endpoints

  • GraphQL for the internal data querying layer where clients have complex, evolving needs

GitHub, Shopify, and Stripe do exactly this. They didn't abandon REST - they added GraphQL where it earned its place.


If your app is mostly CRUD, has a small number of clients, and fairly predictable data needs, REST is usually more than enough.

GraphQL is an excellent tool.
But excellent tools become expensive when used for problems that don’t exist.

Most applications don’t need “future-proof architecture.”
They need simple systems that are easy to ship, debug, and maintain.

— Abhigna
Console Diaries — a developer’s notes
Connect with me on LinkedIn: a6h1gna