Jun 2026 · 3 min read · NestJS · PostgreSQL · Prisma
From NestJS API to Prisma: A Clean Backend for Mobile Apps
Pyin Nyar is the mobile counterpart to my full-stack web work: an Expo / React Native AI tutor backed by a NestJS + PostgreSQL REST API. Where Myanify chose full-stack Next.js to cut latency on a single web-client codebase, Pyin Nyar chose a split topology because the primary client is native and the API needs to evolve, version, and scale independently. Same Prisma discipline, deliberately different topology.
Prisma as the source of truth
Prisma owns migrations and the schema, while service-layer logic (rather than raw SQL scattered through controllers) is where domain rules live. Two concrete wins:
- Schema changes are cleanly reviewable.
- End-to-end type safety means a renamed database column becomes a compile error in the client code rather than a silent runtime
"column does not exist"crash.
The follow-on rule is to keep Zod DTO validation at the boundary — validating untrusted input the moment it enters so controllers stay thin and services focus entirely on domain logic.
Authentication: JWT + guard-level RBAC
Authentication is issued via JWTs, and authorization is enforced using Nest guards at the route boundary rather than through ad-hoc checks inside methods.
The important, easy-to-miss rule: every user-data query must be explicitly scoped to the owner. It is never enough to protect the route if a list endpoint can accidentally return another user's rows. Guard-level enforcement combined with owner-scoped queries is the difference between auth that looks secure and auth that actually is secure.
Moving heavy work out of the request cycle
Anything slow — background reminders, data aggregation, or heavy processing that a client shouldn't block on — goes through Redis-backed BullMQ queues instead of running inline within an HTTP request handler. The API responds instantly even when background tasks pile up, and a retryable queue offers a far more robust failure recovery story than a dropped HTTP request.
Keeping the native mobile client in sync
A native client can easily drift from a changing API. Swagger documentation keeps the contract visible, and versioned responses give mobile apps time to safely migrate. The discipline here is straightforward: document the endpoint, version the contract, and let the client fail loudly on a mismatch rather than failing silently.
What I'd do differently next time
If I approached this project again, I would:
- Implement real-time synchronization (e.g., Postgres
LISTEN/NOTIFYor a lightweight pub/sub) instead of relying purely on polling. - Add request-scoped tracing so queue latency and endpoint latency share a single trace.
- Write contract tests against the generated Swagger specs to catch mobile-breaking changes at CI build time.
Explore the repositories: Mobile Client · Backend API.