Technology Stack
A pragmatic, boring-by-design stack. Financial software rewards reliability over novelty; every choice below is selected for transactional safety and operational maturity.
Frontend
React 18+ with TypeScript
TanStack Query for server-state sync
Tailwind CSS for styling
Vite for bundling
Backend
Python 3.11+ FastAPI OR
Java 17+ Spring Boot 3
OpenAPI 3.0 specification
Pydantic / Jakarta Bean Validation for models
Database
PostgreSQL 13+ (primary)
Redis for cache & sessions
Elasticsearch for search & logs
S3/GCS for file storage
Messaging
RabbitMQ or AWS SQS
Celery (Python) or Quartz (Java)
Event sourcing for audit trail
Deployment
Docker containers
Kubernetes (EKS/GKE)
Terraform for IaC
ArgoCD for GitOps
DevOps
GitHub Actions for CI/CD
SonarQube for code quality
Prometheus + Grafana for monitoring
Vault for secrets
Framework Selection Rationale
- PostgreSQL: Strong ACID guarantees, native JSON support, exclusion constraints for invariants, row-level security
- FastAPI/Spring: Type-safe, async-capable, mature ecosystems; FastAPI for speed, Spring for enterprise scale
- React + TypeScript: Component-driven UI, gradual typing, large talent pool, excellent state-management libraries
- Kubernetes: Multi-region deployment, auto-scaling, self-healing; operational maturity across cloud providers
API Design & Specifications
API-first architecture: the web UI consumes only the public REST API. All integrations, webhooks, and third-party access go through the same API.
REST API Principles
- Versioning: URL-based (e.g., /api/v1/vouchers); semantic versioning
- Format: JSON request/response; HTTP status codes per RFC 7231
- Authentication: Bearer JWT tokens (OAuth 2.0) for users; API keys for service-to-service
- Rate Limiting: 1000 req/min per token; 100 req/min for unauthenticated; per-user concurrency limits
- Pagination: Offset/limit (default 50, max 500); cursor-based for large datasets
- Filtering: Query parameters for common filters; POST body for complex filters
- Sorting: Query parameter sort=field:asc|desc; multiple fields supported
Core Endpoints (Phase 1)
| Resource | Endpoint | Methods | Auth |
|---|---|---|---|
| Companies | /api/v1/companies | GET, POST, PUT, DELETE | User + Tenant |
| Ledgers | /api/v1/ledgers | GET, POST, PUT, DELETE | User + Company |
| Vouchers | /api/v1/vouchers | GET, POST, PUT, DELETE | User + Company |
| Voucher Lines | /api/v1/vouchers/{id}/entries | GET, POST, DELETE | User + Company |
| Reports | /api/v1/reports/{type} | GET | User + Company |
| Parties | /api/v1/parties | GET, POST, PUT, DELETE | User + Company |
| Audit Log | /api/v1/audit-log | GET | User + Admin |
OpenAPI Specification
- Documentation: Auto-generated via OpenAPI 3.0 schema; hosted at /api/docs
- Examples: Request/response examples for every endpoint
- SDKs: Auto-generated client libraries (Python, JavaScript, Java) from spec
- Testing: Postman collection auto-generated from spec
Error Handling
- Format: JSON with code, message, details, timestamp
- Codes: Standard HTTP (200, 201, 400, 401, 403, 404, 422, 500)
- Validation Errors: 422 with per-field error messages (field, code, message)
- Idempotency: Idempotency-Key header for POST/PUT; idempotent operations marked in spec
POST /api/v1/vouchers — Create a voucher with double-entry validation. Request must include date, type, narration, and entries array with balanced debits/credits. API responds with 201 and created voucher object including auto-assigned ID and number.
Data Model & Database Schema
The core is a classic double-entry schema. A Voucher is a header; its Entries (lines) each reference a Ledger and carry a debit or credit amount that must net to zero.
Key Entities
| Entity | Purpose | Key Fields |
|---|---|---|
| Tenant | Customer account; row-level isolation | id, name, plan, created_at |
| Company | Accounting book; child of tenant | id, tenant_id, name, fy_start, base_currency |
| Group | Ledger classification (tree) | id, company_id, name, parent_id, category (Asset, Liability, etc.) |
| Ledger | Individual account | id, company_id, group_id, name, opening_balance, type |
| Voucher | Accounting document | id, company_id, type, number, date, narration, status (draft/posted) |
| Voucher Entry | Line in a voucher (debit or credit) | id, voucher_id, ledger_id, amount, type (dr/cr), narration |
| Party | Customer or vendor | id, company_id, name, type, credit_limit, payment_terms |
| Bill Reference | Outstanding tracking | id, company_id, party_id, bill_number, bill_date, outstanding_amount |
| Audit Log | Immutable change log | id, company_id, entity_type, entity_id, action, user_id, old_values, new_values, timestamp |
Double-Entry Invariant
Database Constraint
Each voucher enforces at the database level:
CHECK (SELECT COALESCE(SUM(CASE WHEN type='DR' THEN amount ELSE 0 END), 0) = COALESCE(SUM(CASE WHEN type='CR' THEN amount ELSE 0 END), 0) FROM voucher_entry WHERE voucher_id = NEW.id)
This prevents any code path (bug or malice) from posting an unbalanced voucher. Application-level validation rejects before insert; database constraint is the final gate.
Indexing Strategy
voucher(company_id, date, type)— For date-range queries and filtering by typevoucher_entry(voucher_id, ledger_id)— For reverse lookups and drill-downvoucher_entry(ledger_id, date)— For ledger statements with balance running totalaudit_log(company_id, created_at)— For compliance audit log queriesparty(company_id, name)— For typeahead search
JSON Columns (for Flexibility)
- voucher.metadata: Custom fields per voucher type (e.g., invoice number for sales, cheque number for payment)
- ledger.settings: Per-ledger config (e.g., currency, control account hierarchy)
- audit_log.context: Contextual info (IP, user agent, API call stack) for security audit
Code Standards & Best Practices
Language-Specific Guidelines
- TypeScript: Strict mode enabled; no `any` type without justification; full type coverage for APIs
- Python: Type hints via pydantic; PEP 8 compliance; black formatter, flake8 linting
- Java: Enterprise coding standards; DRY principle; Spring dependency injection
Repository Structure
- Frontend:
apps/web/— React app;src/{components,pages,hooks,utils} - Backend:
apps/api/— FastAPI/Spring;src/{routes,services,models,middleware} - Shared:
packages/types/— Shared TypeScript types;packages/utils/— Common utilities - Tests: Co-located with source;
*.test.tsor*_test.py - Docs:
docs/— Markdown guides;docs/api/— API specs
Testing Strategy
| Type | Tool | Coverage | Target |
|---|---|---|---|
| Unit | Jest (TS), pytest (Python) | Per function/module | 80%+ |
| Integration | Jest + supertest, pytest | API endpoints + DB | 70%+ |
| E2E | Cypress, Playwright | Full user workflows | Critical paths |
| Load | k6, JMeter | Performance targets | Pre-release |
Code Review & Quality Gates
- GitHub PRs: Minimum 2 approvals before merge; CI/CD checks must pass
- SonarQube: Quality gate: code coverage >80%, no critical/major bugs, duplication <3%
- Linting: ESLint (TS), Black + flake8 (Python), Checkstyle (Java) enforced in pre-commit hooks
- Security: Snyk for dependency scanning; no vulnerabilities in deploy path
Documentation
- Code Comments: Minimal; names should explain intent. Only document WHY (constraints, edge cases)
- Function Docstrings: One-liner for public APIs; parameters and return types
- README: Per package; local setup, testing, deployment instructions
- API Docs: Auto-generated from OpenAPI spec; hosted at /api/docs
Performance & Optimization
Query Optimization
- N+1 Queries: Use ORM eager loading or GraphQL batching
- Pagination: Always paginate large result sets; default 50, max 500
- Caching: Redis for: ledger balances, party masters, tax configs; invalidate on write
- Materialized Views: Pre-aggregate monthly P&L, inventory snapshots; refresh nightly
- Indexes: Regular ANALYZE and index maintenance; slow query logging
API Performance Targets
- Voucher Save: <300 ms including double-entry validation, audit log, and cache invalidation
- Report Generation: <3 s for Trial Balance/P&L/Balance Sheet; async job for larger queries
- Search: Ledger/party typeahead <100 ms; full-text search via Elasticsearch <500 ms
- Login: <2 s including MFA verification
Frontend Performance
- Bundle Size: Main JS <500 KB, CSS <100 KB (gzipped)
- Lighthouse: Score >90 (Performance, Accessibility, Best Practices)
- Core Web Vitals: LCP <2.5s, FID <100ms, CLS <0.1
- Code Splitting: Route-based lazy loading; no large bundles blocking initial render
Resource Monitoring
- CPU: Target <70% under normal load, <85% under peak
- Memory: No memory leaks; graceful degradation above 80% heap
- Database Connections: Connection pooling (10-20 connections per app instance)
- Disk I/O: Monitor for hot spots; archive old audit logs to cold storage
Security Implementation
Authentication & Authorization
- OAuth 2.0 Flow: Authorization Code flow with PKCE for browser clients
- JWT Tokens: RS256 signing; 1-hour access token, 30-day refresh token
- MFA: TOTP via authenticator app; fallback: SMS (rate-limited)
- Session: Secure, HttpOnly, SameSite=Strict cookies; server-side session store
Data Protection
- TLS: TLS 1.2+ on all endpoints; HSTS header; cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- Sensitive Fields: PII (SSN, bank account) encrypted via Vault; keys rotated annually
- SQL Injection: Parameterized queries only; ORM prevents raw string interpolation
- XSS Protection: Content-Security-Policy header; output encoding in templates
- CSRF: SameSite cookies + CSRF tokens for state-changing operations
Secrets Management
- Tool: HashiCorp Vault (or cloud-native: AWS Secrets Manager, Azure KeyVault)
- Rotation: Automatic rotation every 90 days; zero-downtime deployment
- No Secrets in Code: Enforce via pre-commit hooks and CI checks
- Audit Trail: All access to secrets logged and monitored
Vulnerability Management
- Dependency Scanning: Snyk in CI/CD; alert on vulnerable packages
- Static Analysis: SonarQube for code defects; GitHub CodeQL for secrets
- Penetration Testing: Quarterly by external firm; bug bounty program post-launch
- Patch Schedule: Monthly security updates; critical patches within 48 hours
Deployment & CI/CD
CI/CD Pipeline (GitHub Actions)
- Trigger: On PR and push to main/staging/production
- Steps:
- 1. Lint (ESLint, Black, Checkstyle)
- 2. Type Check (TypeScript, mypy, javac)
- 3. Unit Tests (Jest, pytest)
- 4. Security Scan (Snyk, SonarQube, CodeQL)
- 5. Build (compile, bundle, Docker image)
- 6. Integration Tests (API + DB)
- 7. Push to registry (ECR, GCR)
- 8. Deploy to staging (automatic)
- 9. Smoke tests (automatic)
- 10. Manual approval → production
Deployment Process
- Strategy: GitOps with ArgoCD; declarative infrastructure as code (Terraform, Helm)
- Versioning: Semantic versioning (MAJOR.MINOR.PATCH); git tags for releases
- Rollback: One-command rollback to previous version; keep last 5 releases
- Canary Deployments: 5% → 25% → 100% traffic shift over 1 hour
- Database Migrations: Zero-downtime via expand → migrate → contract pattern
Infrastructure as Code
- Terraform: AWS/GCP resources (VPC, RDS, S3, load balancers, KMS)
- Helm Charts: Kubernetes deployments; templated for multi-region
- Versioning: All IaC in git; peer-reviewed pull requests for infrastructure changes
Monitoring & Observability
Metrics (Prometheus)
- Application Metrics: Request latency (histogram), error rate (counter), active requests (gauge)
- Database Metrics: Connection pool usage, query latency, slow queries, replication lag
- Infrastructure: CPU, memory, disk usage, network I/O
- Business Metrics: Vouchers posted, reports generated, failed imports (for trends)
Logging (ELK Stack or Datadog)
- Format: JSON structured logging; includes request_id, user_id, company_id for tracing
- Levels: DEBUG, INFO, WARN, ERROR; filtering by environment
- Retention: 7 days hot (live searches), 90 days archive (Glacier)
- Sensitive Data: PII masked automatically; audit log separate with longer retention
Tracing (Jaeger/X-Ray)
- Instrumentation: All HTTP endpoints and database queries traced
- Sampling: 100% for errors and slow requests; 1% for normal requests (adjustable)
- Correlation ID: Propagated via HTTP headers; links logs, traces, and metrics
Alerting Rules
- Latency: Alert if P99 API latency > 500ms (5-minute window)
- Errors: Alert if error rate > 0.1% (2-minute window)
- Database: Alert if replication lag > 30s; alert if backup fails
- Disk: Alert if usage > 80%; PagerDuty escalation for critical