AJ
Back to Builds
WebIn Progress

RMR Group

Dual-app monorepo for a microfinance organization serving women across India. Public website + internal CMS for loan management with JWT auth, Prisma ORM, and full type safety.

TurborepoNext.js 14PrismaTanStack QueryZod

Overview

RMR Group is a monorepo microfinance platform with two Next.js apps: a CMS (Collection Management System) for internal staff, and a public-facing web app. The CMS handles loan origination, daily payment collection tracking, and agent management for a business where field agents collect small daily installments from borrowers across India.

Why it was built

A client project for a real microfinance operation that was running entirely on spreadsheets. The risks were obvious: no audit trail, no role separation, easy to manipulate figures. The brief was to replace that with a purpose-built internal tool with proper auth, validation, and structured data, starting with Sprint 1 (auth) and building toward full loan lifecycle management.

RS256 Asymmetric JWT Auth

Login issues a JWT signed with a private key. All requests and Next.js middleware verify using only the public key. The CMS never holds the signing key. Keys are base64-encoded PEM strings in env vars, generated by a utility script in the repo.

Role-Based Middleware

Next.js middleware reads the JWT cookie on every request. Routes under /admin/ pass only for SUPER_ADMIN. API routes return 403 for agents. User id, role, and email are forwarded as request headers to downstream handlers.

IP Rate Limiting

An in-memory Map keyed by login:{IP} allows 5 attempts per 15-minute window. Returns Retry-After header with 429 responses. No Redis needed: it resets automatically when the window expires.

Tech Breakdown

Turborepo

Three shared packages (db, validators, types) consumed by both apps. Turborepo's pipeline caching makes this practical: one change to the validators package rebuilds only what depends on it.

Prisma + @rmr/db

The database client, all service functions, and the Prisma schema live in one shared package. This prevents auth logic from diverging between the two apps: hashPassword and verifyJWT are defined once.

RS256 over HS256 JWT

Asymmetric signing means the public-facing app can verify tokens without ever holding the private signing key. A meaningful security decision for a multi-app setup handling financial data.

Zod

Schemas are both the validation logic and the TypeScript type source via z.infer<>. CreateLoanInput is derived from the schema, not written separately. One source of truth.

Vitest + Playwright

Production-grade testing from Sprint 1. Auth is the highest-risk piece: getting it wrong means financial data exposure. Tests run before any code ships.

Challenges
  • 1

    RS256 key pair setup in a Next.js project is non-trivial. Keys are base64-encoded, stored in env vars, decoded and parsed by the jose library on every middleware request. A generate-keys.js utility script exists specifically to help with onboarding.

  • 2

    Role-based routing in Next.js middleware requires handling both API routes (JSON 403) and page routes (redirect to /login) as separate code paths, since getting the response type wrong breaks either the frontend or the API client.

What's Next

Sprint 1 (auth) is complete. Sprint 2 is customer and loan CRUD: the Zod schemas are already written, the API routes and UI are next. After that: payment collection dashboard, agent performance reports, and the public web app.

Want to know more?

Happy to walk you through the details, just reach out.