Back to Projects
NestJS FastAPI Next.js TypeScript Python PostgreSQL +8 more

Mint

Polyglot Fintech Microservices Platform

Production-style fintech platform with authentication, wallet payments, KYC, fraud detection, analytics, social features, webhooks, an admin console, and a tamper-proof audit log - all connected through gRPC and Kafka.

Mint screenshot 1Mint screenshot 2Mint screenshot 3Mint screenshot 4Mint screenshot 5Mint screenshot 6

Mint is a full-stack fintech platform built across 12 independently deployable services. It covers the full lifecycle of a digital wallet - from identity verification and fraud scoring to social payments, budget analytics, and a tamper-proof audit log - all wired together through synchronous gRPC and asynchronous Kafka.

Overview

The platform is polyglot by design: auth and wallet are FastAPI (Python), everything else is NestJS (TypeScript), and the frontend is Next.js 15. Services communicate synchronously over gRPC when a result is needed before proceeding (fraud scoring, KYC limit checks, wallet settlement), and asynchronously over Kafka for fan-out side effects (notifications, analytics, audit logging, webhooks). Every service has its own isolated PostgreSQL database.

Key Features

Authentication & Identity

  • JWT (RS256) with JWKS - Auth service signs tokens with an RSA private key; all other services verify locally against the public JWKS endpoint, no round-trips required
  • KYC Tiers - Users progress through UNVERIFIED -> BASIC -> VERIFIED; each tier unlocks higher per-transaction and monthly spend limits
  • Document Upload - KYC submissions are stored in MinIO (S3-compatible); admins review and approve/reject via a dedicated queue
  • Admin Role - Separate JWT claim gates the admin console; admin service enforces it on every request

Transaction Engine

  • Fraud Scoring - Every transaction calls the fraud service via gRPC before settlement. Returns ALLOW / REVIEW / BLOCK with a 0-100 score and the rules that fired
  • KYC Limit Enforcement - Transactions service calls KYC service via gRPC to check per-transaction and daily/monthly limits before debiting
  • Atomic Settlement - Wallet debit and credit happen over gRPC; both must succeed or the transaction fails
  • State Machine - PENDING -> PROCESSING -> COMPLETED / FAILED / CANCELLED / REVERSED
  • Idempotency - Idempotency-Key header; responses cached in Redis for 24 hours so retries are safe

Social Payments

  • Money Requests - Request funds from contacts with a 7-day expiry (BullMQ scheduled job). Accepting a request automatically triggers a transfer via Kafka -> transactions service
  • Bill Splits - Create a split, assign per-participant amounts, track individual payments
  • Contacts - Managed contact list scoped per user

Analytics & Budgets

  • Spend Categorisation - Completed transactions are auto-classified into FOOD, TRANSPORT, ENTERTAINMENT, UTILITIES, OTHER by merchant name and description matching
  • Monthly Aggregates - Category totals and top merchants updated in real-time from Kafka events
  • Budget Alerts - Users set per-category budgets; the analytics service publishes a Kafka event when a threshold is crossed, triggering a notification

Notifications & Webhooks

  • Real-Time SSE - In-app notification stream over Server-Sent Events; nginx disables proxy buffering so events land immediately
  • Persistent Feed - Notifications stored in DB, readable as a paginated list with read/unread state
  • User Webhooks - Users register HTTPS endpoints and subscribe to event types; payloads are HMAC-signed, delivered with exponential backoff retry via BullMQ

Admin Console

  • User Management - Search, freeze/unfreeze accounts, update roles
  • KYC Review Queue - Inspect submitted documents, approve or reject (emits Kafka event -> KYC service updates tier)
  • Fraud Review Queue - Review REVIEW-scored transactions, approve or block
  • Transaction Operations - List, reverse, or force-complete stuck transactions
  • Global Limits - Configure platform-wide transaction limits without a deployment

Audit Log

  • Immutable by Design - PostgreSQL trigger prevents UPDATE and DELETE on the audit table; every service publishes to audit.events
  • Full Traceability - Each entry preserves the OpenTelemetry trace ID so any log entry can be correlated back to the original distributed trace in Grafana

Observability

  • Distributed Tracing - Every service ships OTLP traces to a local collector -> Grafana Tempo. A single top-up produces one trace spanning fraud scoring, KYC checks, wallet settlement, and all Kafka consumers
  • Trace Propagation - W3C Trace Context over HTTP, gRPC metadata for gRPC calls, custom KafkaTraceInterceptor writes/reads trace headers on Kafka messages so async boundaries don’t break the trace

Architecture

graph TD
  Client -->|HTTP| nginx

  nginx -->|/| web
  nginx -->|/app-admin| web
  nginx -->|/api/v1/auth| auth
  nginx -->|/api/v1/wallet| wallet
  nginx -->|/api/v1/transactions| transactions
  nginx -->|/api/v1/kyc| kyc
  nginx -->|/api/v1/analytics| analytics
  nginx -->|/api/v1/notifications SSE| notifications
  nginx -->|/api/v1/social| social
  nginx -->|/api/v1/webhooks| webhook
  nginx -->|/admin| admin

  transactions -->|gRPC :50052| fraud
  transactions -->|gRPC :50053| kyc
  transactions -->|gRPC :50051| wallet

  admin -->|gRPC :50053| kyc
  admin -->|gRPC :50051| wallet
  admin -->|gRPC :50052| fraud

Communication Patterns

Mint uses two distinct internal communication patterns depending on whether the caller needs a synchronous result.

Synchronous - gRPC is used when the caller must have a result before continuing:

CallerTargetPurpose
transactionsfraudScore every transaction before settlement
transactionskycCheck per-transaction and daily/monthly limits
transactionswalletDebit sender, credit recipient atomically
adminkycFetch KYC profiles and pending review queue
adminwalletFetch wallet status for user management
adminfraudFetch and action fraud cases

Asynchronous - Kafka is used for fan-out side effects where the producer doesn’t need to wait:

TopicKey Consumers
auth.eventswallet (create), kyc (create profile), notifications, audit
transaction.eventsanalytics, notifications, webhook, audit
wallet.eventsnotifications, webhook, audit
kyc.eventsnotifications, audit
social.eventstransactions (auto-transfer on accept), notifications, audit
analytics.eventsnotifications (budget alerts), webhook
admin.eventskyc (approve/reject tier), audit

Services

ServiceStackPortDescription
webNext.js 153000User app and admin console
authFastAPI4001JWT issuance, refresh, RBAC
walletFastAPI + gRPC4002 / 50051Balance management, atomic settlement
transactionsNestJS4003Transfers, top-ups, idempotency
fraudNestJS (gRPC only)50052Real-time rules-based fraud scoring
kycNestJS + gRPC4004 / 50053Document upload, tier management
analyticsNestJS4005Spend insights, category budgets
notificationsNestJS4006Persistent notifications + SSE stream
socialNestJS4007Contacts, money requests, bill splits
webhookNestJS4008User-registered webhooks + delivery log
adminNestJS4009Admin console API
auditNestJS4010Immutable append-only audit log

Getting Started

# Clone the repository
git clone https://github.com/sreekarnv/mint.git
cd mint

# Generate RSA keys (first time only)
sh scripts/generate-keys.sh

# Start all services
docker compose -f docker-compose.dev.yml up -d --build

# Create an admin user
docker exec -it mint-auth uv run python /app/apps/auth/src/create_admin.py \
  --email admin@mint.dev \
  --password adminpass \
  --name "Admin User"

The user app is at http://localhost and the admin console at http://localhost/app-admin. Grafana traces are at http://localhost:4000.

What I Learned

Building Mint at this scale forced me to confront the real costs of distributed systems:

  • Two communication protocols serve different needs - gRPC for synchronous calls where you need a result (fraud scoring, limit checks, settlement) and Kafka for async fan-out where you don’t. Conflating them leads to either tight coupling or unnecessary latency
  • Polyglot is genuinely useful, but the integration surface is the hard part - Python services handle the stateful, mutation-heavy wallet and auth work well, but wiring gRPC stubs, shared proto definitions, and trace propagation across runtimes requires discipline
  • Observability must be designed in, not bolted on - Getting a single trace to span HTTP -> gRPC -> Kafka required explicit propagation at every boundary. Without it, async failures are nearly impossible to debug in production
  • Idempotency is not optional in financial systems - Network retries are guaranteed to happen; building safe retry handling from the start saved considerable pain later
  • The audit log is the source of truth - Having an immutable, append-only log that every service writes to independently made debugging event ordering issues far easier than reading application logs

Tech Stack

NestJS
FastAPI
Next.js
TypeScript
Python
PostgreSQL
Apache Kafka
Redis
gRPC
Docker
NGINX
OpenTelemetry
Grafana
MinIO