Technology Stack
| Layer | Technology | Version/Details |
|---|---|---|
| Frontend | Next.js | 16.0.7 |
| Backend | Express | 4.18.2 |
| API Layer | tRPC | 11.7.2 |
| Database | PostgreSQL | 15 Alpine |
| ORM | Prisma | 5.18.0 |
| Cache | Redis | 7 Alpine |
| Authentication | NextAuth.js | 5.0.0-beta.30 |
| Validation | Zod | 3.22.4 |
| Forms | React Hook Form | 7.68.0 |
| State | TanStack Query | 5.90.12 |
| Payments | Razorpay | 2.9.6 |
| Styling | Tailwind CSS | 4.x |
| Monorepo | Turborepo | Latest |
| Package Manager | pnpm | 8+ |
| Deployment | Docker, NGINX | Latest |
Purpose and Philosophy
The Town Committee Management System (TCMS) is a comprehensive platform designed to digitize and streamline civic operations for town committees and municipal bodies. The system embodies the principle that government services should be accessible, transparent, and efficient for citizens while providing powerful administrative tools for committee staff.
The project follows a multi-tenant architecture supporting multiple town committees with complete data isolation. Each tenant operates independently with their own branding, configuration, and user base while sharing the same underlying infrastructure. This SaaS-like model enables cost-effective deployment for multiple municipalities.
Core Design Principles
- Citizen-Centric Services: All citizen-facing features prioritize accessibility, mobile-first responsiveness, and intuitive user experience. Services like building permits, room rentals, and lease agreements guide users through multi-step wizard processes.
- Type-Safe Development: The monorepo uses tRPC for end-to-end type safety between frontend and backend. Shared packages (
shared-types,validators,utils) ensure consistent types across all applications.
- Aadhaar Integration: Real-time Aadhaar-based citizen identification enables duplicate prevention, automatic form population, and secure identity verification for all citizen services.
- Payment Flexibility: The system supports both online (Razorpay) and offline (cash/bank transfer) payment modes with admin verification workflows, ensuring accessibility for all citizens regardless of digital literacy.
- Role-Based Access Control: Three-tier RBAC (Developer, Admin, Citizen) ensures proper separation of concerns. Developers manage platform configuration, Admins handle daily operations, and Citizens access services.
Architecture Deep Dive
Monorepo Structure
TCP/ (Town Committee Project)
├── apps/
│ ├── web/ # Next.js 16 frontend
│ │ ├── app/ # App Router pages
│ │ │ ├── page.tsx # Landing page
│ │ │ ├── auth/ # Authentication pages
│ │ │ ├── permit/ # Building permits
│ │ │ ├── room-rent/ # Room rentals
│ │ │ ├── lease/ # Lease agreements
│ │ │ ├── lease-sale/ # Lease transfers
│ │ │ ├── services/ # Service requests
│ │ │ ├── admin/ # Admin dashboard
│ │ │ └── developer/ # Developer console
│ │ ├── components/ # React components
│ │ └── lib/ # Utilities & API client
│ │
│ └── api/ # Express + tRPC backend
│ ├── src/
│ │ ├── index.ts # Express server entry
│ │ ├── router/ # tRPC routers
│ │ ├── procedures/ # tRPC procedures
│ │ └── prisma/ # Database client
│ └── prisma/
│ └── schema.prisma # Database schema
│
├── packages/
│ ├── shared-types/ # Shared TypeScript interfaces
│ ├── validators/ # Shared Zod validation schemas
│ └── utils/ # Shared utility functions
│
├── docker/
│ ├── local.compose.yml # Local development
│ └── prod.compose.yml # Production deployment
│
└── nginx/
└── nginx.conf # Production reverse proxyDatabase Schema
The Prisma schema defines comprehensive data models for all civic operations:
Citizen Management:
model Citizen {
id String @id @default(uuid())
aadhaarNumber String @unique
aadhaarHash String? @unique // Secure lookups
name String
mobileNumber String @unique
email String? @unique
fullAddress String
kycStatus String @default("PENDING")
// Credit Management
totalCreditAmount Decimal @default(0)
availableCreditAmount Decimal @default(0)
buildingPermits BuildingPermit[]
roomRentals RoomRental[]
leaseAgreements LeaseAgreement[]
leaseSales LeaseSale[]
serviceRequests ServiceRequest[]
payments Payment[]
}Building Permits:
model BuildingPermit {
id String @id @default(uuid())
applicationNo String @unique
citizenId String
landDagNo String?
landPattaNo String?
buildingType String?
buildingCategory String?
buildupArea Decimal?
applicationStatus String?
applicationFee Decimal?
scrutinyFee Decimal?
totalAmountDue Decimal?
}Multi-Tenancy Models:
model Tenant {
id String @id @default(uuid())
developerId String
name String
slug String @unique
adminId String @unique
pricingTierId String
isWhiteLabeled Boolean @default(false)
customDomain String?
branding TenantBranding?
tenantUsers TenantUser[]
}
model PricingTier {
id String @id @default(uuid())
developerId String
name String
monthlyPrice Decimal?
yearlyPrice Decimal?
maxUsers Int?
maxStorageGb Int?
tierFeatures TierFeature[]
tenants Tenant[]
}tRPC API Structure
The backend uses tRPC for type-safe API communication:
// Public procedures
export const publicProcedure = t.procedure;
// Protected procedures (Admin)
export const adminProcedure = t.procedure.use(isAdmin);
// Router example
export const appRouter = router({
// Citizen services
citizen: router({
create: publicProcedure.input(createCitizenSchema).mutation(...),
getByAadhaar: publicProcedure.input(aadhaarSchema).query(...),
}),
// Building permits
permit: router({
create: publicProcedure.input(permitSchema).mutation(...),
list: adminProcedure.query(...),
updateStatus: adminProcedure.input(statusSchema).mutation(...),
}),
// Room rentals
roomRent: router({
create: publicProcedure.input(rentalSchema).mutation(...),
list: adminProcedure.query(...),
}),
// Leases
lease: router({
create: publicProcedure.input(leaseSchema).mutation(...),
transfer: publicProcedure.input(transferSchema).mutation(...),
}),
// Payments
payment: router({
initiate: publicProcedure.input(paymentSchema).mutation(...),
verify: publicProcedure.input(verifySchema).mutation(...),
verifyOffline: adminProcedure.input(offlineVerifySchema).mutation(...),
}),
});Key Features and Functionality
1. Citizen Services
Building Permission (/permit)
- 5-step wizard: Applicant → Land Details → Building Details → Fees → Receipt
- Dynamic fee calculation based on building category
- Application tracking and status updates
Room Rental (/room-rent)
- 3-step process: Tenant → Room & Rent Details → Receipt
- Aadhaar-based tenant verification
- Monthly rent tracking with ledger
Lease Agreements (/lease)
- 4-step wizard: Applicant → Property → Terms → Receipt
- Aadhaar citizen lookup with duplicate prevention
- Payment frequency selection (Quarterly/Yearly)
Lease Sale/Transfer (/lease-sale)
- 5-step process: Seller → Buyer → Property → Premium → Confirmation → Receipt
- Dual Aadhaar verification for both parties
- Premium amount calculation
Service Requests (/services)
- NOC, Corrections, Complaints, General Queries
- Single-page form with dropdown selection
2. Authentication & Authorization
NextAuth.js Integration:
- Credentials provider for email/mobile login
- Session-based authentication
- JWT tokens for API security
Role-Based Access:
- Developer: Platform configuration, tenant management, pricing tiers
- Admin: Daily operations, citizen management, payments, reports
- Citizen: Service applications, payment submissions, application tracking
Protected Setup:
- One-time system initialization at
/setup - Password-protected with SETUP_PASSWORD
- 4-step wizard: Auth → Database → Admin User → Developer User
3. Payment Processing
Online Payments (Razorpay):
- Integration with Razorpay SDK
- Signature verification for webhook security
- Real-time payment status tracking
Offline Payments:
- Cash and Bank Transfer support
- Proof document upload
- Admin verification workflow
- Manual approval/rejection
Receipt Generation:
- Professional PDF receipts via react-to-print
- Unique receipt numbers
- Downloadable and printable
4. Admin Dashboard
Comprehensive admin panel with:
- Overview: Revenue statistics, permit counts, rental status, service requests
- Permits: Review and approve building permits
- Rentals: Track room rentals and occupancy
- Leases: Manage lease agreements and payments
- Services: Handle citizen service requests and NOCs
- Users: Search, filter, and manage registered citizens
- Settings: Configure pricing rules and fee structures
5. Developer Console
Platform-level controls:
- Tenant Management: View, edit, and manage all tenants
- Platform Branding: Customize logo, colors, themes
- Payment Configuration: Global Razorpay keys and UPI settings
- Email System: SMTP configuration for system emails
- System Controls: Maintenance mode, debug flags
6. Multi-Tenancy
Each town committee (tenant) gets:
- Isolated database records (via tenantId)
- Custom branding (logo, colors, favicon)
- Custom domain support
- Pricing tier allocation
- Subscription management
Database Models Overview
| Domain | Models |
|---|---|
| Identity | Citizen, User, Tenant, TenantUser |
| Building | BuildingPermit, FeeStructure |
| Rental | RoomRental, RentPayment, RentLedger |
| Lease | LeaseAgreement, LeasePayment, LeaseLedger, LeaseSale |
| Payments | Payment, CreditLedger |
| Services | ServiceRequest, SupportTicket |
| Multi-Tenancy | Tenant, TenantBranding, PricingTier, Feature, TierFeature |
| Settings | DeveloperSettings, AdminSettings, SystemSettings |
| Audit | AuditLog, CitizenAuditLog, TicketAuditTrail |
Development and Operations
Local Development
# Install dependencies
pnpm install
# Start database services
docker-compose -f docker/local.compose.yml up -d
# Setup database
cd apps/api
npx prisma generate
npx prisma migrate dev --name init
# Start all apps
pnpm devProduction Deployment
# Production Docker
docker-compose -f docker/prod.compose.yml up -d --build
# Environment variables required:
# DATABASE_URL, REDIS_URL, SETUP_PASSWORD
# RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET
# NEXTAUTH_SECRET, NEXTAUTH_URLArchitecture
Internet
↓
NGINX (Port 80/443)
├── Rate Limiting (100 req/s)
├── Security Headers
└── SSL/TLS
↓
Next.js App (Port 3000)
├── Frontend (SSR)
└── tRPC API
↓
PostgreSQL (5432) + Redis (6379)Security Implementation
Authentication
- NextAuth.js v5 with credentials provider
- bcrypt password hashing
- Session-based authentication
- JWT token management
API Security
- tRPC middleware for procedure protection
- Bearer token authorization
- Input validation with Zod schemas
Infrastructure
- NGINX rate limiting (100 req/s)
- Security headers (X-Frame-Options, XSS Protection)
- Redis password protection
- Environment variable secrets
Audit Trail
- Complete audit logging for admin actions
- Citizen audit logs for profile changes
- Ticket audit trails for support requests
Lessons Learned and Best Practices
What Worked Well
- tRPC Type Safety: End-to-end type safety eliminates runtime errors and improves developer experience
- Multi-Tenant Isolation: Clean separation enables safe multi-organization deployment
- Zod Validation: Shared validators in packages ensure consistent validation across frontend and backend
- Multi-Step Wizards: Progressive form design improves completion rates for complex applications
Areas for Enhancement
- Real-time Updates: Could add WebSocket for live status notifications
- Mobile Apps: React Native wrapper would extend accessibility
- Advanced Reporting: Could add more sophisticated analytics dashboards
Conclusion
The Town Committee Management System represents a comprehensive solution for municipal digitization. With its multi-tenant architecture, type-safe monorepo structure, and extensive citizen services, it provides a robust foundation for efficient civic administration.
The system successfully balances citizen accessibility with administrative control, offering features like Aadhaar verification, flexible payment options, and comprehensive audit trails. The production-ready deployment with Docker and NGINX ensures reliability and scalability for growing municipalities.
Implementation Details
API Endpoints Overview
The tRPC API provides comprehensive endpoints for all services:
Public Endpoints (No Auth Required):
citizen.create: Register new citizen with Aadhaar verificationcitizen.findByMobile: Search citizen by mobile numbercitizen.findByAadhaar: Search citizen by Aadhaarpermit.create: Submit building permit applicationroomRent.create: Register room rentallease.create: Create new lease agreementlease.transfer: Transfer lease ownershipserviceRequest.create: Submit civic service requestpayment.initiate: Initiate online payment via Razorpaypayment.verifyWebhook: Verify payment webhook signaturesetup.initialize: One-time system initialization
Protected Endpoints (Admin Required):
permit.list: Get all permits with filterspermit.updateStatus: Approve/reject building permitpermit.getById: Get single permit detailsrental.list: Get all room rentalsrental.update: Update rental detailslease.list: Get all lease agreementslease.getById: Get lease detailspayment.verifyOffline: Verify offline payment proofcitizen.list: List all registered citizenscitizen.manualVerify: Admin manual KYC verificationserviceRequest.list: Get all service requestsserviceRequest.updateStatus: Update request status
Protected Endpoints (Developer Only):
tenant.list: Get all tenantstenant.create: Create new tenanttenant.updateSettings: Update tenant configurationpricing.list: List pricing tierspricing.create: Create pricing tierdeveloper.updateSettings: Update platform settingsdeveloper.updateBranding: Update platform branding
Data Flow Examples
Building Permit Application Flow:
- Citizen fills 5-step wizard form
- Frontend validates each step with Zod schemas
- On final submission, tRPC mutation calls permit.create
- API generates unique application number
- Fee calculation based on building category and area
- Payment page shows options (Online/Offline)
- On successful payment, permit status updates to "submitted"
- Admin receives notification, reviews, and updates status
Citizen Registration with Aadhaar:
- Citizen enters Aadhaar number
- API checks for existing record (duplicate prevention)
- If new, creates citizen record with "PENDING" KYC status
- Mobile/email verification links sent
- Admin can manually verify via admin dashboard
- KYC status updates to "VERIFIED"
Form Validation Strategies
The system uses Zod schemas for validation across both frontend and backend:
// Example: Building Permit Schema
export const buildingPermitSchema = z.object({
applicantType: z.enum(["OWNER", "TENANT", "OTHER"]),
landDagNo: z.string().min(1, "Dag number required"),
landPattaNo: z.string().optional(),
mouza: z.string().optional(),
revenueCircle: z.string().optional(),
landArea: z.string().min(1, "Land area required"),
ownershipType: z.enum(["INDIVIDUAL", "JOINT", "INSTITUTION"]),
locationRoadName: z.string().min(1, "Road name required"),
locationWardNo: z.string().min(1, "Ward number required"),
buildingType: z.enum(["RESIDENTIAL", "COMMERCIAL", "INDUSTRIAL", "MIXED"]),
buildingCategory: z.enum(["A", "B", "C", "D"]),
proposedUse: z.string().min(1, "Proposed use required"),
numFloors: z.number().min(1).max(10),
buildupArea: z.number().positive(),
});Credit Management System
The system includes a credit wallet for citizens:
model CreditLedger {
id String @id @default(uuid())
citizenId String
transactionType String // PURCHASE, USED, REFUND, ADJUSTMENT
amount Decimal
debitAmount Decimal @default(0)
creditAmount Decimal @default(0)
runningBalance Decimal?
referenceTable String?
referenceId String?
description String?
createdAt DateTime @default(now())
createdByUserId String? // Admin who created the credit
}This enables:
- Advance payments for services
- Credit transfers between citizens
- Refund processing
- Adjustment for erroneous charges
Pricing and Subscription Model
Multi-tenant pricing tiers allow different municipalities to choose features:
| Tier | Monthly Price | Max Users | Features |
|---|---|---|---|
| Basic | ₹5,000 | 5 | Core services |
| Standard | ₹10,000 | 20 | + Reports, Analytics |
| Premium | ₹25,000 | Unlimited | + Custom domain, White-label |
Each tier enables specific features defined in the TierFeature model.
Docker Development Environment
The local development uses Docker Compose for database services:
# docker/local.compose.yml
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: town_committee
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/dataProduction NGINX Configuration
The production deployment uses NGINX as reverse proxy:
# nginx/nginx.conf (simplified)
server {
listen 80;
server_name tcms.example.com;
# Rate limiting
limit_req zone=api burst=100 nodelay;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Proxy to Next.js
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}This configuration includes:
- Rate limiting (100 requests per second)
- Security headers (X-Frame-Options, CSP, etc.)
- WebSocket support for hot reload
- Proper proxy buffering
Summary
The Town Committee Management System (TCMS) demonstrates a production-grade approach to government digitization. Key accomplishments include:
- Type-safe monorepo with tRPC ensuring end-to-end TypeScript coverage
- Multi-tenant architecture enabling multiple municipalities on one platform
- Comprehensive citizen services covering permits, rentals, leases, and service requests
- Flexible payment options supporting both online and offline transactions
- Complete audit trail for accountability and transparency
- Production-ready infrastructure with Docker and NGINX
The project serves as a template for similar government digitization initiatives, providing a balance of accessibility for citizens and control for administrators while maintaining technical excellence through modern development practices.
Architecture Feedback
Spotted a potential optimization or antipattern? Let me know.