DEV Community

Cover image for REST vs GraphQL: Why I Use Both (And You Probably Should Too)
Chizi
Chizi

Posted on

REST vs GraphQL: Why I Use Both (And You Probably Should Too)

For a while now... I've been building APIs. I used to think REST was all I needed. Every API I built was with REST. Simple express routes like /api/auth, /api/v1/auth, /api/users, /api/v1/users, /api/dashboard, /api/etc. It was clean, predictable and I never really saw a reason to ask any questions. After all, I was quite comfortable with it.

Then I started working on a particular project. And it completely changed how I thought about APIs. For those who might be curious... it’s an escrow platform and I had been working on for some time. Yeah... it was kind of my project for my final year as a computer science student. It's a project to help fight the high rate of scams in Nigeria.

The idea was quite simple: Someone wants to buy something expensive — a laptop, phone, whatever. Instead of sending money directly and praying, they send it through the platform. We hold it in escrow. Only release when both parties confirm delivery or service rendered. It's meant to solve the "I sent the money, they blocked me" epidemic that's far too well known.
Does using the blockchain solve this issue? Yes, using the blockchain could solve this perfectly, but facing the reality: some Nigerians still think of "crypto" as a scam. So I was going traditional: Next.js frontend, Node.js backend, Paystack for payments, PostgreSQL + Prisma ORM + Redis, the usual stack and GraphQL API architecture.

That's when things got messy. GraphQL is incredible... until payment provider needs to tell you someone paid.

This is me going from "REST is all I need" → "GraphQL will save me" → "why am I like this." If you don't know what a resolver is, don't worry. I didn't either.

First: What Even Is REST? (Like, Actually)

REST is just normal URLs doing normal things.

GET     /users/123           → give me user 123
GET     /users/123/orders    → give me their orders
POST    /transactions        → create a new transaction

Enter fullscreen mode Exit fullscreen mode

You make many requests, you get fixed responses, you stitch everything together in the frontend. That's it. It's boring but it works. It's reliable. It's what every bank, every payment gateway, every SMS provider uses.

How I Actually Met GraphQL

I didn't learn about GraphQL because I wanted to. I learned it because I had to.

About a year ago, I was brought onto a project as the lead frontend developer. Web app, nothing crazy. I was excited.

The backend lead sends me the API documentation. and let me tell you it wasn’t the usual URL to a swagger documentation I was used to; it was the actual Api URL where I first saw what the GraphQL playground/apollo studio looked like.

I opened the URL I see... I stared at it for a solid five minutes. What the hell is a query? What's a mutation? Why is there only one endpoint? Where are the REST routes. I'd heard of GraphQL before. Saw it mentioned on Twitter. Read a blog post once that I didn't finish. But I had never actually used it. And now I was supposed to build an entire frontend with it.

I didn't want to look stupid even though I was told I could ask questions if i had any, so I didn't bother asking too many questions. Just nodded in the meeting like "yeah, GraphQL, cool, got it." Then I went home and googled and chatgpt-ed "what is GraphQL" like my life depended on it.

Learning GraphQL While Building (The Hard Way)

The project forced me to figure it out fast.And honestly? Once I got past the initial "what is happening" phase, it was kind of amazing.
Instead of this:

// Fetch user
const userRes = await fetch('/api/users/123');
const user = await userRes.json();

// Fetch their orders
const ordersRes = await fetch(`/api/users/123/orders`);
const orders = await ordersRes.json();

// Fetch order details for each order
const orderDetails = await Promise.all(
  orders.map(order => 
    fetch(`/api/orders/${order.id}`).then(r => r.json())
  )
);
Enter fullscreen mode Exit fullscreen mode

I could do this:

query GetUserWithOrders($id: ID!) {
  user(id: $id) {
    name
    email
    orders {
      id
      total
      status
      items {
        name
        price
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

One request. Everything I needed. No overfetching. No underfetching.
My frontend code got so much cleaner. And from then I became a believer. After that project ended, I started using GraphQL for everything. Rewrote old projects. Told people REST was outdated. Posted about it. I was that guy.

Then: What Is GraphQL? (From Someone Who Was Confused)
GraphQL is one single endpoint (usually /graphql) where you write exactly what you want:

One request. Everything I need. No extra fields. No missing data.
When I first wrote this query for the transaction page on the frontend and saw it return exactly what the UI needed, I actually said "wow" out loud in an empty room.

I became obsessed. Spent some time converting everything to GraphQL. Killing REST endpoints. Writing resolvers. Setting up Apollo. Converting to GraphQL types.

I was convinced I would'nt touch REST for a while.

Then came my escrow project. And I was ready. GraphQL from day one. Beautiful, nested queries. Type safety everywhere. The whole setup.

Until reality hit.

The Day Reality Slapped Me in the Face, Paystack Didn’t Speak GraphQL

Then I made a test payment.

Paystack needed to tell my server "hey, payment successful, hold the funds."

But Paystack doesn't send GraphQL queries.

They send a plain POST request to a REST endpoint:

POST https://domain.com/webhooks/paystack

Not to /graphql. Just a normal URL with JSON in the body.

Same with Stripe. Same with Termii for SMS delivery reports. Same with literally every external service I needed to integrate. I sat there for two days straight trying to make Apollo Server accept raw webhook payloads and trigger GraphQL mutations internally.

It was absolute hell.

I tried middleware. I tried wrapping REST routes around GraphQL resolvers. I tried some weird hybrid setup that made my code look like an actual spaghetti.

Nothing felt right.

Eventually, at 2 AM on the fourth day, I gave up and wrote this:

router.post('/webhooks/paystack', async (req, res) => {
  const signature = req.headers['x-paystack-signature'] as string;
  const payload = req.body;

  // Verify signature
  const hash = crypto
    .createHmac('sha512', process.env.PAYSTACK_SECRET!)
    .update(JSON.stringify(payload))
    .digest('hex');

  if (!signature || hash !== signature) {
    return res.status(400).json({ status: 'invalid' });
  }

  // Handle successful payment
  const reference = payload?.data?.reference;
  if (reference) {
    await escrowService.handleSuccessfulPayment(reference);
  }

  return res.status(200).json({ status: 'success' });
});

export default router;
Enter fullscreen mode Exit fullscreen mode

A plain REST route.

In my "pure GraphQL" app, you could say I felt dirty. Like I' ha betrayed everything I had just learned. But you know what? The money moved. The escrow updated. Users would be able to get their confirmations. It worked. And that's when I finally accepted the truth.

5 Things REST Still Does Better Than GraphQL (Sorry, Not Sorry)

*1. Webhooks from payment/SMS/logistics providers
*

Every payment gateway, SMS provider, logistics company, bank API — they all use REST webhooks. No exceptions. None of them are switching to GraphQL anytime soon. If you want to build, you play by their rules.

*2. External and partner Integrations
*

When other developers want to build on your platform, they expect normal REST URLs. Simple GET and POST requests. Documentation they can understand in five minutes. GraphQL requires explaining queries, mutations, variables, fragments. There's a learning curve. Sometimes you don't want that friction.

3. Caching

GET /transactions/abc123 can be cached by Cloudflare automatically. By browsers. By CDNs.

GraphQL queries are POST requests to /graphql. CDNs don't cache POST requests by default. You need persisted queries, custom hashing, special configuration. Possible? Yes. Simple? Absolutely not.

*4. File Uploads (multer >> GraphQL multipart hell)
*

Uploading ID cards, proof of delivery photos, profile pictures — way easier with multer and REST endpoints. GraphQL can handle file uploads. I tried it once. Gave up after an hour (although i did find a way in a different project i’m working on). Used REST. Life got easier immediately.

*5. Simple Public Endpoints and Health Checks
*

Health checks. Status pages. Metrics endpoints. Public API routes for partners.

REST is perfect here. Clean. Simple. Everyone understands it.

4 Things GraphQL Absolutely Destroys REST At

1. Deeply Nested Data

My escrow transaction page needs buyer info + seller info + transaction details + payment records + message history + dispute status + activity logs.

One GraphQL query vs 7-8 REST calls.

On Nigerian internet where connections drop randomly? Those extra round trips aren't just slower. They fail. A lot.

*2. Mobile Performance
*

Building a React Native version. On 2G/3G networks, every request drains battery. Every extra round trip adds seconds to load time. GraphQL's single request model isn't a nice-to-have. It's a requirement.

3. Different Screens, Different Needs

Admin dashboard wants everything. Mobile app only needs 5 fields. User profile needs something else entirely. Same GraphQL query. Different field selections. No new endpoints.

REST? You either overfetch and waste bandwidth, or you create custom endpoints for every screen and drown in maintenance.

*4. Type Safety Everywhere
*

I run graphql-code-generator on my schema.

Get perfect TypeScript types on frontend and backend. Automatically. For free.

No more "wait, is it phone or phoneNumber?" No more guessing response shapes. No more runtime surprises.

The editor knows everything. Autocomplete works perfectly. Refactoring is safe.

This alone saves me hours every week.

How I Actually Decide Now

Use GraphQL when:

  • I control both the client and server (my Next.js frontend)
  • Data relationships are complex and nested
  • Multiple screens need different data shapes
  • Type safety is important
  • Mobile performance matters
  • I want fast iteration without creating new endpoints

Use REST when:

  • Receiving webhooks from external services (payments, SMS, etc.)
  • Uploading files (ID verification, proof of delivery)
  • Building public APIs for third-party developers
  • Simple health checks or status endpoints
  • Integrating with services that only speak REST
    Use both when:

  • Building anything with payment gateways

Which is basically everything I touch now.

What I'm Actually Using in 2025

For this escrow project (and future serious projects):

  • ExpressJS/NestJS (one codebase, GraphQL module + REST controllers side by side)
  • Apollo Server for GraphQL
  • GraphQL for 90% of frontend-backend communication
  • REST only for:
    • All webhook endpoints
    • File upload endpoints
    • Partner integration APIs
  • Prisma ORM with PostgreSQL
  • Redis for job queues (webhook retry logic is a must)
  • GraphQL Code Generator for TypeScript types everywhere

For quick side projects:

  • Simple admin dashboard? Pure REST, done in a weekend
  • Complex mobile app? Pure GraphQL from day one
  • Anything involving payments? Hybrid architecture from the start

The Uncomfortable Truth

I was wrong.

GraphQL didn't kill REST. It moved in and took the master bedroom, sure. But REST still handles the gate, the doorbell, the security system, and all the deliveries from the outside world.

They're not enemies. They're roommates. Sometimes they annoy each other, but they make the house work.

And in 2025, if you're building anything real especially in markets where payments and third-party integrations are everything, the winning move is knowing exactly when to use which tool. No purity contests. Just working software that solves real problems.

What This Journey Taught Me

Starting out, I thought choosing an API architecture was like choosing a religion. You pick one and defend it forever.

GraphQL good, REST bad. Or vice versa.

But building real products in the real world teaches you something else: pragmatism beats purity every single time.
The best architecture is the one that:

• Solves your actual problems
• Works with your actual constraints
• Ships on time
• Doesn't make your life miserable

Sometimes that's pure REST. Sometimes it's pure GraphQL. Usually it's both.

And that's perfectly fine.

If You're Just Starting Out

Don't stress about GraphQL vs REST.

Seriously. Don't. I wasted weeks worrying about "the right architecture" when I should've just been building. Start simple. Use what you know. REST is fine. GraphQL is fine. Pick one and ship something.
You'll know when you need the other one. The pain will tell you. For me, the pain was making 8 REST calls to load one transaction page. That's when GraphQL made sense. Then the pain was Paystack webhooks not working with my pure GraphQL setup. That's when REST came back.

Let the problems guide your decisions. Not Xthreads. Not Medium articles. Not even this one.

The Questions I Ask Myself Now

Before I pick an architecture for a new project, I ask:

Am I building the frontend too?
• Yes → GraphQL makes my life easier
• No, it's a public API → REST is probably better

*Do I need to receive webhooks?
*
• Yes → I need REST endpoints, no way around it
• No → Pure GraphQL could work

Is the data deeply nested?
• Yes (user → orders → items → reviews) → GraphQL shines here
• No (simple CRUD) → REST is faster to set up

Am I building for mobile?
• Yes → GraphQL saves me from network round trips
• No, just web → Either works fine

Do I care about type safety?
• Yes → GraphQL + Code Generator is incredible
• No → REST with good validation is fine

How much time do I have?
• Tight deadline → Use what I know best, ship fast
• Building for the long term → Set up both properly from day one

These questions cut through all the noise. They force me to think about my actual needs instead of what's trendy.

What I Wish Someone Told Me Earlier

1. You're allowed to mix them.
Nobody's going to arrest you for having GraphQL and REST in the same codebase. I promise.
Some people online act like mixing them is a crime. It's not. It's just engineering.

2. Start with REST, add GraphQL later if needed.
GraphQL has more setup. More concepts. More tooling.
REST is simpler to start with. Every developer knows it.
If REST starts hurting, then migrate the painful parts to GraphQL. You don't have to rewrite everything at once.

3. Webhooks will force your hand.
If you're building anything with payments, SMS, email delivery, logistics tracking you will need REST endpoints. Plan for this from day one. Don't be like me, fighting reality at 2 AM.

4. The "pure" approach always fails eventually.
I've tried pure REST. Ran into overfetching hell. I've tried pure GraphQL. Ran into webhook hell. Now I use both. My code is "impure." And it works better than ever.

5. Nobody cares about your API architecture except you.
Users don't care if you use REST or GraphQL or both or neither. They care if your app is fast. If it works. If it solves their problem. The tech stack is invisible to them. As it should be.

The Paystack Integration (Since People Keep Asking)

I'm still in test mode ( i don't think i would though). Haven't gone live yet. But here's what I learned from testing:

1. Verify the webhook signature ALWAYS
Paystack sends an x-paystack-signature header. You hash the payload with your secret key and compare.

If they don't match, reject it. Someone's trying to fake a payment.

const hash = crypto
  .createHmac('sha512', process.env.PAYSTACK_SECRET)
  .update(JSON.stringify(payload))
  .digest('hex');

if (hash !== signature) {
  return { status: 'invalid' };
}
Enter fullscreen mode Exit fullscreen mode

2. Webhooks retry. A LOT.
If your endpoint doesn't respond with 200, Paystack retries. Multiple times. So make your webhook handler idempotent. Don't credit the same payment twice. I use the payment reference as a unique key. Check if I've already processed it. If yes, return success without doing anything.

const existingPayment = await this.prisma.payment.findUnique({
  where: { gatewayReference: payload.data.reference }
});

if (existingPayment?.status === 'SUCCESS') {
  return { status: 'success' }; // Already processed
}

Enter fullscreen mode Exit fullscreen mode

3. Update fast, process later
Don't do heavy processing in the webhook handler. Mark the payment as successful, return 200 immediately, then process in a background job.
Paystack's webhook has a timeout. If you take too long, they assume it failed and retry. I mark it as paid, queue a job in Redis, then let the job handle notifications, etc. Fast response. Reliable processing.

4. Test in production-like conditions
Ngrok is great for local testing, I think I was given a free domain which I usually use to test in dev mode like prod. But also test on a real server with a real domain.

The Mental Shift: They’re Not Competitors — They’re Tools for Different Jobs

Here's what finally clicked for me:

GraphQL and REST aren't competing solutions to the same problem.
They solve different problems.

GraphQL solves: "My frontend needs flexible, efficient data fetching."
REST solves: "The outside world needs to talk to my server in a standard way."

Once I stopped seeing them as competitors and started seeing them as tools for different jobs, everything made sense.

It's like arguing whether a hammer or a screwdriver is better. Better for what? They're both useful. Use the right one for the job.

What's Next for Me

I'm still building this escrow platform. Still in test mode with Paystack. Still fixing bugs. Still learning.

Planning to:
• Build the React Native mobile app (GraphQL will shine here)
• Maybe open up a public REST API for other developers later

It's a long journey. But every problem I solve teaches me something new.
And honestly? The hybrid architecture feels right now. It doesn't feel like a compromise anymore. It feels like the correct solution.

What About You?

Are you team REST? Team GraphQL? Team "I use both and stopped apologizing for it"?What problems have you run into? What made you switch approaches?

I'd genuinely love to hear your story. Drop a comment. Let's talk about this.

Because honestly, I think a lot of us are struggling with the same questions. We're just not talking about it because we're afraid of looking like we don't have it all figured out.

But none of us have it all figured out. We're all just building stuff, breaking stuff, and learning as we go.

And that's fine.

Final Thoughts: Use the Right Tool for the Job (Often That Means Both)

If you take away anything from this article, let it be this:

Use the right tool for the job. Even if that means using multiple tools.

REST isn't dead. GraphQL isn't a silver bullet. They're both just tools.

And the best developers I know? They're not religious about their tools. They're pragmatic.

They use what works. They ship products. They solve real problems for real people.
Everything else is just noise.

So go build something. Use REST. Use GraphQL. Use both. Use neither.

Just build something that works. Something that helps someone. Something you're proud of.

That's what matters.

Building something Let's connect. Seriously. I'm figuring this stuff out as I go, and I'd love to share notes with other builders. You can connect with me on Linkedin and X.

Think I'm completely wrong about something? Tell me. I'm still learning. Maybe you know something I don't. I'm genuinely interested.

Want to see the code? You can find it here on my github. Fyi the code might be a bit of a mess.. ha-ha.

P.S. If you're still putting /api/v1/ or /api/v2/ in every single URL in 2025, we need to talk. There are better ways to handle API versioning. But that's a whole other article.

Thanks for reading. Now go build something.

Top comments (0)