Skip to main content
ArchitectureSecurityNext.js

Architecting Resilience: Navigating Microservices with API Gateways, Message Queues, & Advanced Patterns

elango
elango

Full Stack Engineer

Updated
5 min read
Architecting Resilience: Navigating Microservices with API Gateways, Message Queues, & Advanced Patterns

When I read articles about microservices resilience, they usually jump straight to Kafka clusters and Istio sidecars. That is valid for teams running dozens of services, but it is not how I operate elangodev.com. My production stack is a Next.js App Router deployment on Vercel talking to Supabase PostgreSQL. Still, the same design instincts apply: treat every inbound request as untrusted, isolate write paths, and make failures predictable instead of silent.

Why I Split Contact Messages from Authenticated Chat

On my site I run two communication surfaces that look similar to users but behave differently under the hood. The public contact form posts to /api/messages and stores rows in a simple messages table. The authenticated chat system posts to /api/chat/send and fans out through Supabase Realtime. I deliberately kept these as separate route handlers rather than one generic "send message" endpoint because their threat models diverge. Contact submissions are anonymous; chat sends require bearer tokens, sender identity checks, block lists, and optional push notifications.

This is my version of an API gateway pattern at application scale. Each route is a narrow gateway that validates shape, authenticates where required, and returns a consistent JSON envelope before touching the database. I do not expose admin read access on the contact route at all — only the chat admin flow can list conversations.

Securing the Public Messages Gateway

The contact route is deceptively simple, which is why I treat it as a resilience boundary. Every POST must include email, fullname, and message text. I reject incomplete payloads immediately with HTTP 400 so bad bots do not reach Supabase. For GET, I require a bearer token, verify the JWT with the admin client, and compare the email against ADMIN_EMAIL before returning rows. That three-step gate — token presence, user resolution, role check — is the same sequence I use anywhere admin data leaves the database.

// app/api/messages/route.js — admin-only GET gateway
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || "b.elango93@gmail.com";
if (authData.user.email !== ADMIN_EMAIL) {
  return new Response(JSON.stringify({ error: "Forbidden" }), {
    status: 403,
    headers: corsHeaders,
  });
}

On the client, my contact page wraps the fetch in try/catch and surfaces server errors through toast notifications instead of failing quietly. That small UX layer is part of resilience: users know whether their message entered the queue.

Chat Send Validation as a Stateful Gateway

The chat send route is where I stack most of my validation logic. After resolving the user from either cookie session or Authorization header, I enforce that sender_id matches the authenticated user ID. Without that check, a stolen session token could impersonate another user. I also gate admin-only flags: only my admin email can mark is_admin_reply. Non-admin senders hit a block-list lookup against admin settings before insert.

// app/api/chat/send/route.js — identity and payload gates
if (user.id !== sender_id) {
  return new Response(
    JSON.stringify({
      success: false,
      error: "Sender ID mismatch. Nice try!",
    }),
    { status: 403, headers: corsHeaders },
  );
}

if (!sender_id || (!message && !attachment_url)) {
  return new Response(
    JSON.stringify({
      success: false,
      error: "Message or attachment is required",
    }),
    { status: 400, headers: corsHeaders },
  );
}

Notice I insert through the admin Supabase client only after identity checks succeed. That pattern — verify first, elevate privileges second — prevents Row Level Security misconfigurations from becoming catastrophic.

Error Handling and Async Side Effects

Resilience is not only about blocking bad requests; it is about not losing good ones. My chat UI uses optimistic updates with rollback: a temporary message renders instantly, and if /api/chat/send returns 401 or network failure, I restore the draft text and remove the temp row. On the server, push notification dispatch runs inside its own try/catch so a web-push failure never rolls back the database insert. I treat notifications as an asynchronous side effect, similar to publishing an event after a message queue ack.

For contact POST failures I return generic 500 responses in production while logging details server-side. Exposing stack traces publicly would aid attackers mapping my infrastructure. In development I attach error.message to help me debug locally on feature branches.

What I Would Add Before Kafka

If elangodev.com chat traffic grew tenfold, my next step would not be a managed Kafka cluster. I would add rate limiting at the edge, dead-letter logging for failed inserts, and a Supabase database webhook to process heavy work asynchronously. Those patterns mirror message-queue benefits — decouple, retry, observe — without operational overhead I do not yet need.

Microservices textbooks talk about circuit breakers between services. On my monolith-plus-BaaS architecture, the circuit breaker is explicit HTTP status handling, bounded retries in fetchWithServerRetry, and refusing to coupling critical writes to optional notification code. That discipline kept my contact inbox and chat stable through Vercel cold starts and mobile network drops.

When you architect a personal production site, resilience means designing gateways that match real abuse patterns, not copying enterprise middleware because the diagram looks impressive. My routes are small, auditable, and optimized for the actual failures I see in logs — stale tokens, mismatched sender IDs, and blocked users — not theoretical broker outages.

I review these handlers quarterly the same way I review Supabase RLS policies: short, explicit, and tied to real traffic on elangodev.com.

elango

Written by

elango

Full Stack Engineer & Software Architect specializing in React, Next.js, and high-performance web applications.

Share on:

Discover More

View blog