A CRM and analytics platform for Amazon sellers and their teams — products, suppliers, orders, shipments, freelance requests, chats and AI tools — built as three Node.js services: REST API, background queue and real-time gateway.
My role
Lead backend developer and architect across all three services — API, queue and real-time gateway.
Stack
Node.js
Fastify
BullMQ
Socket.IO
PostgreSQL
Redis
OAuth 2.1
MCP
The problem
Seller teams mix many roles — owners, supervisors, researchers, buyers, storekeepers, freelancers — working on the same products, orders and shipments. The platform has to show each of them exactly what they are allowed to see, react in real time, run heavy reports and AI jobs in the background, and let external AI assistants work with the data safely.
Architecture
Clients call the Fastify API with short-lived JWTs and keep a WebSocket open to the Socket.IO gateway. The API writes to PostgreSQL and hands long or heavy work — reports, chat fan-out, AI generation, scheduled jobs — to BullMQ workers through Redis. The API and the workers push real-time events through the gateway, which runs on several instances behind the Redis adapter. A separate PostgreSQL holds Amazon reports written by an external parser, and the queue recalculates aggregates from it into the main database. AI assistants connect through MCP with OAuth 2.1.
Key decisions
01Long AI jobs as asynchronous tasks with long-polling
Why
Generation takes minutes while the proxy drops connections after 60 seconds, so already-paid results were being lost. Now the request returns 202, the client long-polls, Redis pub/sub wakes it early and the database row stays the source of truth. Each request carries an idempotency key, and jobs are never retried automatically — a retry means paying twice.
Trade-off
More moving parts — job statuses, a sweeper for stuck jobs — and budgets can only be enforced softly.
02Access control in layers, not in one place
Why
URL permissions, roles, ownership scopes in repositories, field-level masking at serialization and notification recipients are each checked where they belong. An MCP key gets the intersection of its own scope and the owner's current permissions, so an AI assistant can never see more than the user.
Trade-off
Every new endpoint has to be registered in several layers.
03Row locks in a fixed order for shipments
Why
Two browser tabs recreating the same box used to create duplicates and double the stock. Recreation now runs in a transaction with SELECT … FOR UPDATE in a fixed order and a conditional update; the losing request gets a 409 instead of corrupting data.
Trade-off
Every endpoint that touches boxes has to go through the same guard.
04Read cursors instead of a row per reader
Why
Storing who read each message grows with every message times every participant. One cursor per user per chat — and per thread — keeps unread counters simple and the table small.
Trade-off
Unread rules live in both the API and the gateway and must stay in sync.
What I built
Three services — Fastify REST API, BullMQ workers and a Socket.IO gateway — sharing PostgreSQL and Redis
OAuth 2.1 + PKCE provider and MCP server, so Claude and ChatGPT can work with CRM data within the user's permissions
Five layers of access control: URL permissions, roles, entity ownership, field-level masking and notification recipients
Short-lived access tokens with refresh rotation, multi-session limits, sub-users, admin impersonation and SSO for internal tools
Asynchronous AI generation with long-polling, idempotency keys, cancellation and per-user and per-team budgets
Chats with threads, reactions, pins and read cursors on a multi-instance Socket.IO with the Redis adapter
Separate reports database fed by an external Amazon data parser, with scheduled recalculations in the queue
Zero-downtime migrations: concurrent indexes, NOT VALID foreign keys and lock timeouts