MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

CinemaSins: Everything Wrong With The Wiz In 15 Minutes Or Less

2025-11-25 08:00:25

Everything Wrong With The Wiz In 15 Minutes Or Less

CinemaSins is back with their signature “Everything Wrong With…” breakdown, this time tackling the 1978 musical fantasy The Wiz. Riding the hype of Wicked’s return to theaters, they cruise down the yellow brick road, nitpicking plot holes, cheesy moments and questionable special effects in rapid-fire fashion.

Alongside the video, they’re pushing viewers to explore more CinemaSins channels (TVSins, CommercialSins, CinemaSins Podcast Network), fill out a quick poll, support them on Patreon, and follow their social media for all things sinful cinema.

Watch on YouTube

CinemaSins: Everything Wrong With KPop Demon Hunters In 16 Minutes Or Less

2025-11-25 08:00:17

Everything Wrong With KPop Demon Hunters In 16 Minutes Or Less is a tongue-in-cheek CinemaSins video where Jeremy and the crew gleefully tally up every “sin” in this K-Pop–infused demon-slaying flick. Expect rapid-fire snark, pop-culture references and enough nitpicks to fill a demon’s soul.

Beyond the roast, they’re hustling you to explore Cinemasins.com, hit up their Linktree for polls and Patreon support, and follow spinoff channels (TVSins, CommercialSins, CinemaSins Podcast Network). You can also connect with the writers on Twitter/Instagram and join the community on Discord, Reddit, Instagram and TikTok.

Watch on YouTube

Independent musicians are leaving Spotify in droves

2025-11-25 07:57:37

These 2 posts are just a tiny slice of what I've seen on my Instagram feed just within the past few days. Each post is getting huge engagement and gives some details as to what is going on. A quick read of the comments indicates artists are starting to focus more on sharing music from and driving traffic to their own websites, Bandcamp (which has remained a champion of indie musicians consistently for 17 years now), and other artist-friendly platforms.


Announcing Cloud SQL free trial instances: Experience the power of a fully managed database

2025-11-25 07:52:04

Cloud SQL is a proven foundation for fully managed databases, offering production-ready MySQL, PostgreSQL, and SQL Server database engines without the operational headache. With Cloud SQL, there’s no need to worry about patches, backups, and scaling limits — just connect your app and start building.

Today, we’re announcing new free trial instances designed to help you experience the power of Cloud SQL for MySQL and PostgreSQL, with no upfront commitment. Whether you're a seasoned Google Cloud developer or new to the platform, this 30-day free trial allows you to explore, test, and truly understand the value Cloud SQL brings to your database needs.

There are two editions of Cloud SQL currently available:

  • Cloud SQL Enterprise Plus edition: Designed for mission-critical applications, providing the highest performance and availability with a 99.99% SLA (including maintenance). It features near-zero downtime for planned maintenance, significant performance boosts through Data Cache (using local SSD), and enhanced write throughput.
  • Cloud SQL Enterprise edition: Suitable for most business applications, offering high availability and managed maintenance with a 99.95% SLA. It offers all the core capabilities of Cloud SQL, striking a good balance of performance, availability, and cost.

Cloud SQL Free Trial Instance ‘Get Started’ Page

Cloud SQL Free Trial Instance ‘Get Started’ Page

 

Why a dedicated Cloud SQL free trial?

You might be familiar with the standard $300 Google Cloud free trial for new users. While that's a fantastic starting point, customers have been asking us for a more specialized offering. They want a dedicated environment to test the full power of Cloud SQL, especially enterprise-grade configurations for Performance, High Availability (HA), and Data Cache. This new trial is our answer.

This trial provides a significantly enhanced experience for customers developing applications on top of Cloud SQL, allowing you to:

  • Experience enterprise-grade features: Test critical functionality like High Availability and Data Cache, both essential to robust and performant database operations.
  • Onboard new users: As a developer, get hands-on with Cloud SQL without the usual hurdle of getting expense approvals for running tests.
  • Perform preliminary performance testing: Evaluate Cloud SQL's performance for your specific workloads, ensuring it meets your demands.

This new Cloud SQL free trial is designed for a wide range of users:

  • Existing Google Cloud customers: If you're already using other Google Cloud products, but haven't explored Cloud SQL, this is your chance!
  • New Google Cloud users: Complementing the existing standard $300 trial, this offers a deeper dive into Cloud SQL's capabilities.

What's included in the 30-day free trial?

We want you to get a comprehensive understanding of Cloud SQL's key value pillars: price-performance, high availability, connectivity, security, observability, ease of manageability, and open-source compatibility. Your free trial instance will be configured to help you explore all of these areas, based on the following database instance:

feature list

When you’re ready to move your workload to production, upgrading to a paid instance is a simple [one-click upgrade](https://cloud.google.com/sql/docs/postgres/upgrade-cloud-sql-instance-to-enterprise-plus-in-place), at any time during the trial.

Not ready to upgrade quite yet? At the end of the 30-day free trial, we automatically suspend your free trial resources, keeping the instance in a "stopped" state for an additional 90 days, at no additional charge. This should give you ample time to upgrade and continue without interruption.

Ready to get started?

Ready to unlock the full potential of your data with Cloud SQL? Creating your free trial instance is easy. If you’re new to Google Cloud, just sign up for an account and follow the instructions to create your Cloud SQL free trial instance. This exciting offer is available in all Google Cloud regions. Start your free trial and see what Cloud SQL can do for your applications.

Building a Comprehensive E2E Test Suite with Playwright: Lessons from 100+ Test Cases

2025-11-25 07:51:43

The Journey

While developing the platform for LiveSpaces, I identified a critical gap in our delivery workflow. As the application grew, our reliance on manual verification and our existing CI/CD checks proved insufficient. We needed a way to streamline release cycles without sacrificing quality.

I took the initiative to build a comprehensive End-to-End (E2E) test suite using Playwright, but I underestimated the complexity. It wasn't just about clicking buttons; it was about handling authentication flows, managing device states, and dealing with third-party integrations securely and reliably.

From authentication flows to device management, every feature presented unique challenges. Here is a retrospective on the architecture I built to solve these bottlenecks, the decisions I made, and the lessons I learned.

Prerequisites

This post focuses on advanced patterns and architectural decisions rather than basic installation. I assume you are familiar with the basics of Playwright or similar E2E tools (Cypress, Selenium).

If you are brand new to Playwright, I highly recommend checking out the official documentation first.

Quick Context:
The examples below use:

  • Framework: Playwright (Node.js)
  • Language: JavaScript/TypeScript
  • Pattern: Page Object Model (POM)

What I Built

100+ Test Cases Across 6 Major Areas

  1. Email Signup Flow — The complete journey from registration to business setup.
  2. OAuth Integration — Signup and signin flows with comprehensive mocking.
  3. Device Pairing — Device pairing logic using 6-character OTP codes.
  4. Device Management — Renaming, deleting, and managing device settings.
  5. Account Type Selection — Complex modal handling for onboarding flows.
  6. Business Information Forms — Industry, demographics, and capacity selection logic.

Key Challenges & Solutions

Challenge 1: Email Verification in Tests

The Problem: I could not access real email inboxes (Gmail/Outlook) reliably in automated tests. Using real inboxes made the tests slow, flaky, and prone to security blocks.

The Solution: I implemented a smart mocking strategy.

  • Development: I mocked the email verification endpoint to return a fixed success response.
  • Production/Integration: I documented hooks for services like Mailosaur for when real delivery testing is strictly necessary.

Code Example:

// Mock the verification code endpoint to bypass email delivery
await page.route('**/api/verification/send', route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify({
      success: true,
      code: '123456' // Fixed mock code for testing
    })
  });
});

Challenge 2: OAuth Testing

The Problem: Automating third-party providers (Google, Facebook) is often a violation of their Terms of Service. Furthermore, dealing with 2FA, captchas, or external popups leads to incredibly flaky tests.

The Solution: Comprehensive OAuth flow mocking. Instead of visiting the provider, I intercepted the callback that the provider would have sent to our application.

Code Example:

// Mock the OAuth callback
await page.route('**/api/auth/oauth/callback**', route => {
  const url = new URL(route.request().url());

  // If the app is trying to exchange a token, give it a mock user
  if (url.searchParams.has('access_token')) {
    route.fulfill({
      status: 200,
      body: JSON.stringify({
        jwt: 'mock_jwt_token',
        user: { id: 123, email: '[email protected]' }
      })
    });
  }
});

Challenge 3: Integration vs. Mocked Tests

The Problem: I needed fast feedback loops during development (mocks) but actual verification before deployment (integration). I didn't want to maintain two separate test suites.

The Solution: Dual-mode test files. I designed the tests to auto-detect the presence of an authentication token in the environment variables to decide whether to mock network requests or hit the real backend.

The Logic Flow:

Dual-mode flowchart

Code Example:

const AUTH_TOKEN = process.env.AUTH_TOKEN;
const USE_REAL_BACKEND = !!AUTH_TOKEN;

test.beforeEach(async ({ page }) => {
  if (USE_REAL_BACKEND) {
    console.log('Using REAL backend');
    // No mocks; allow requests to pass through
  } else {
    console.log('Using MOCKED mode');
    // Apply mocks defined in separate helper files
    await applyNetworkMocks(page); 
  }
});

Challenge 4: Test Data Cleanup

The Problem: Integration tests running against a real backend leave behind "zombie" data (e.g., created devices), causing subsequent tests to fail due to duplicate name errors or database bloat.

The Solution:

  • Strict try/finally blocks for guaranteed cleanup within the test.
  • (Pro Tip: For larger suites, moving this logic into Playwright Fixtures is the preferred pattern).

Code Example:

test('should create and manage device', async ({ page, request }) => {
  let testDeviceId = null;

  try {
    // 1. Create test device via API
    const response = await request.post(`${API_URL}/devices`, {
      headers: { Authorization: `Bearer ${AUTH_TOKEN}` },
      data: { name: 'Test Device', code: 'TEST123' }
    });
    testDeviceId = (await response.json()).data.id;

    // 2. Test the UI flow interacting with this device...
    await page.reload();
    await expect(page.getByText('Test Device')).toBeVisible();

  } finally {
    // 3. Always cleanup, even if the assertion above fails
    if (testDeviceId) {
      await request.delete(`${API_URL}/devices/${testDeviceId}`, {
        headers: { Authorization: `Bearer ${AUTH_TOKEN}` }
      });
    }
  }
});

Challenge 5: Async Component Loading

The Problem: Third-party components (maps, heavy charts) often load asynchronously. Tests were failing because they tried to interact with elements before they were truly interactive.

The Solution: Utilizing Playwright's Web-First Assertions. Unlike manual timeouts (waitForTimeout), these assertions automatically retry until the condition is met or the timeout is reached.

Code Example:

const mapInput = page.getByTestId('map-input');

// Bad Practice: 
// await page.waitForTimeout(1000);

// Best Practice: Wait for state, not time
// This automatically waits for the element to be in the DOM, visible, AND enabled
await expect(mapInput).toBeEnabled({ timeout: 10000 });

// Now safe to interact
await mapInput.click();

Challenge 6: Portal-Based UI Components

The Problem: Modern UI libraries (like Radix UI or Headless UI) often render dropdowns and modals in "portals" at the bottom of the <body> tag, physically outside the component that triggered them.

The Solution:

  • I used specific ARIA roles.
  • I scoped locators correctly to escape the current container and search the document root.

Code Example:

// The dropdown trigger is in the main container
await page.getByRole('button', { name: 'Options' }).click();

// The menu itself is in a portal at the document root
// We wait for the menu specifically to be visible
const menu = page.getByRole('menu');
await expect(menu).toBeVisible();

// Click the item inside the menu
await menu.getByRole('menuitem', { name: 'Rename' }).click();

What Worked Really Well

1. Page Object Model (POM) Pattern

Centralizing selectors in reusable classes was the single highest-ROI decision I made. When the UI changed (and it did often), I updated one file instead of 20.

The Architecture:

Page Object Model architecture

Code Example:

// pages/AuthPage.js
export class AuthPage {
  constructor(page) {
    this.page = page;
    this.emailInput = page.getByPlaceholder('[email protected]');
    this.loginButton = page.getByRole('button', { name: 'Login' });
  }

  async login(email, password) {
    await this.emailInput.fill(email);
    await this.loginButton.click();
  }
}

2. Comprehensive Documentation

Tests without documentation become legacy code the moment they are written. I created 15+ markdown files covering "How to run," "How to mock," and "How to debug." This dramatically reduced onboarding time for future developers and prevented "fear of the test suite."

3. Error Case Testing

Testing the "Happy Path" is easy. Testing the "Sad Path" is where value lies. I systematically mocked 404s, 500s, and network timeouts to ensure the UI handled errors gracefully (e.g., showing Toast notifications) rather than crashing.

4. Dual-Mode Testing

By using environment variables to switch between Mocks and Real Backends, a single test file serves as both a Unit-like Test (Mocked: fast, stable, deterministic) and an Integration Test (Real: slower, verifies backend contracts).

Key Learnings & Tips

  1. Mock External Services: Never test Google's login page. Mock the OIDC response. You are testing your app, not Google's uptime.
  2. Prioritize User-Visible Locators: Use getByRole and getByText over CSS classes (.btn-primary). This mimics how users find elements and makes tests resilient to styling refactors.
  3. Always Cleanup: Test data pollution causes flaky tests. If you create it, delete it (preferably in a finally block or a fixture).
  4. Wait Properly: Never use waitForTimeout(5000). If you find yourself doing this, you are likely missing a proper state assertion or an await expect(...).

The Numbers

  • Test Files: 10+
  • Test Cases: 100+
  • Lines of Code: ~5,000+
  • Coverage Areas: 6 major features
  • Confidence Level: High

Further Reading

If you want to dive deeper into the concepts mentioned here, I recommend these resources:

This post is based on real-world experience building a production E2E suite. All code examples are simplified for clarity.

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

2025-11-25 07:24:31

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

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())
  )
);

I could do this:

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

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;

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' };
}

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
}

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.