easy-pass: EasyPass Mini: The SaaS App Where Postgres Is the Real Backend

A multi-tenant service tracker that pushes security, permissions, and ERP sync logic into migrations, then lets the Next.js app stay almost embarrassingly thin.

8 min read • View on GitHub • More from nvmaditya

A wide editorial illustration of a slim frontend desk in the foreground, while the true machinery sits beneath the floor in a locked Postgres vault. Tenant rooms branch off through guarded channels, and one route passes a policy gate while another routes invoice crates through a timestamp ledger. The image explains that the database, not the UI, is enforcing isolation and truth.
EasyPass Mini looks like a normal app on the surface. The real enforcement layer lives under the floor in Postgres.
Key Takeaways

The database is the product

EasyPass Mini looks like a normal Next.js app until you notice where the real decisions happen. The important rules are not in route handlers. They live in Supabase migrations, where Postgres decides who can see a row, who can write it, and whether an incoming invoice is newer than the one already stored.

That matters because SaaS apps fail in predictable ways. Tenancy leaks happen in code paths nobody reviewed. Sync jobs quietly overwrite good data with stale exports. This repo tries to remove both classes of failure by moving the boundary into the database.

A close-up editorial illustration of two stamped invoices on a workbench, one fresh and one stale. A mechanical gate compares their timestamps and only lets the newer one through into a ledger, while duplicate crates are sorted one by one behind it. The image explains how the sync layer refuses older ERP truth and keeps the newest record.
The sync engine is not optimized for speed. It is optimized for surviving duplicates, out-of-order exports, and stale updates.

Why multi-tenancy belongs in Postgres

The security model is the sharpest part of the repo. Instead of asking the app to remember every company filter, EasyPass Mini uses Row Level Security and a helper like is_company_member to decide access inside Postgres. That is a stronger place to make the decision because every query has to pass through it.

The interesting trick is the use of SECURITY DEFINER. That avoids recursive policy traps when the policy itself needs to check membership against a protected table. In plain English: the database can verify membership without getting stuck trying to obey its own guardrails.

Both the auth path and the sync path converge on the same idea: Postgres is the place where truth gets accepted or refused.

This is the payoff. A normal controller-heavy backend would let the database store data and the app decide what is allowed. EasyPass Mini flips that. The app becomes a client of policy, not the owner of policy.

create policy "company members can read requests"
  on requests for select
  using (is_company_member(company_id));

create policy "admins can insert requests"
  on requests for insert
  with check (has_company_role(company_id, 'admin'));

The sync engine is designed for ugly ERP data

The invoice pipeline is where the repo stops looking like a demo and starts looking like infrastructure. The sync is idempotent, ordered, and defensive. It processes rows one at a time because messy exports often contain duplicate IDs, and a bulk statement can choke on rows that target the same record twice.

The core write pattern is classic, but the guardrail is the real story. ON CONFLICT (external_id) DO UPDATE gives the import its identity. The WHERE excluded.updated_at > invoices.updated_at clause decides whether a newer invoice is allowed to replace an older one. That means stale truth loses, even if it arrives later.

Conventional app-layer backendEasyPass Mini
Permissions live in controllers or middlewarePermissions live in Postgres RLS
Tenant filters can be forgotten in one queryTenant isolation applies to every query
Imports often trust the latest file blindlyImports reject stale rows by timestamp
Duplicate ERP records can break bulk writesSequential RPC calls collapse duplicates safely
Security is spread across app codeSecurity is centralized with database policy
insert into invoices (external_id, company_id, amount, updated_at)
values ($1, $2, $3, $4)
on conflict (external_id)
do update set
  amount = excluded.amount,
  updated_at = excluded.updated_at
where excluded.updated_at > invoices.updated_at;

The auth bridge keeps the thin app honest

The middleware is doing less than a conventional backend layer, but that is the point. It refreshes the session, redirects unauthenticated users to login, and keeps the protected routes aligned with Supabase auth state. The app stays thin because the session boundary is centralized instead of duplicated across pages.

That makes the UI feel simple without making the system naïve. Route protection is still necessary. It just is not trusted to be the only protection.

Why this feels like a template for disciplined AI-assisted development

The .agents directory is a clue that the repo is more than a codebase. It is a set of rules, patterns, and constraints that can be reused by humans or agents. That is the useful version of AI-assisted development: not autocomplete theater, but encoded discipline.

The upside is consistency. The risk is rigidity. A project like this works when the rules are specific enough to prevent subtle mistakes, but not so specific that every change becomes a ceremony.

What EasyPass Mini does better than a conventional SaaS stack

The trade-off is clean. You give Postgres more responsibility, and you get fewer places for security and sync bugs to hide. That is especially attractive for a multi-tenant B2B tool where auditability matters and the data model is stable enough to deserve first-class enforcement.

QuestionConventional stackEasyPass Mini
Where is trust enforced?In app codeIn database policy and RPCs
What happens to stale imports?Often overwritten or ignored ad hocExplicitly rejected by timestamp
How visible is tenancy?Scattered across routesEncoded in RLS and helper functions
What does the frontend do?Carry business rulesCall into a thin, honest UI