What it is and what it isn’t
FakeStore BaaS has no frontend. It’s a headless API — merchants integrate via API key and build any interface they want on top. The “Fake” in the name isn’t pejorative: it’s a reference to the public FakeStore API used in tutorials, which this project replaces with a real, multi-tenant, production-ready version.
Why RLS and not WHERE clauses
The most important decision in the project was where to implement data isolation. The conventional approach is to add WHERE tenant_id = $1 to every application query. It works — until someone forgets, or a JOIN bypasses the filter, or a debug query runs in production without the tenant context.
With RLS, PostgreSQL refuses any access to rows outside the active tenant in the transaction — regardless of what the Node.js code does. The mechanism:
SET LOCAL app.current_tenant_id = '42';
-- From this point on, any SELECT/INSERT/UPDATE/DELETE
-- automatically filters tenant_id = 42
-- Rollback or end of transaction clears the context
The RLS integration test suite verifies this explicitly: it creates two tenants with separate data and confirms that queries from one return nothing from the other — even with raw SQL.
Webhook worker in a separate process
The webhook engine runs in a completely separate Docker container from the API. When an order is confirmed, the API pushes a job to the BullMQ queue. The worker consumes asynchronously, signs the payload with HMAC-SHA256, and delivers with exponential retry (up to 5 attempts, base 60s). Every delivery — success or failure — is recorded in webhook_events.
This means a client endpoint that takes 10 seconds to respond cannot block any API request. And the client can audit exactly when each webhook was delivered and with what HTTP response.
Money as Integer
All monetary values in the database are cents (Integer). R$ 49.90 becomes 4990. Percentage discounts are basis points: 15% = 1500. Conversion to reais only happens in the JSON response. This eliminates 0.1 + 0.2 = 0.30000000000000004 structurally — no developer needs to remember to use toFixed(2).