Wiki
Technology Stack & Design Rationale
Why QuoteNode uses Java, Spring Boot, and PostgreSQL — and what trade-offs that implies for hosting and performance.
Technology Stack & Design Rationale
QuoteNode’s technology choices are driven by security, correctness, and long-term maintainability — not by minimizing resource consumption or chasing the latest framework trends.
This page explains what we use, why we chose it, and what that means for deployment.
Why Java 25 and Spring Boot 4
Security as the primary driver
QuoteNode handles sensitive commercial data: customer records, pricing strategies, offer negotiations, and business relationships. The technology stack must be battle-tested in environments where security failures have real consequences.
Java and Spring Boot are the default choice in banking, insurance, healthcare, and government — industries where security is not optional. This is not an accident:
- Mature security model — Spring Security provides a proven, audited framework for authentication, authorization, session management, CSRF protection, and security headers. We do not need to build these primitives from scratch.
- Type safety — Java’s strong type system catches entire categories of bugs at compile time that would be runtime errors in dynamically typed languages. When calculating offer totals involving multiple currencies, VAT rates, and discount rules, type safety is not a luxury.
- Memory safety — The JVM provides automatic memory management with garbage collection, eliminating buffer overflows, use-after-free, and other memory corruption vulnerabilities that affect C/C++ and, in some contexts, Go codebases.
- Ecosystem maturity — Libraries like Flyway (migrations), Thymeleaf (PDF templates), Hibernate (ORM), and Testcontainers (integration testing) are maintained by organizations with long track records.
Why not Go, Rust, or Node.js?
Each of these would be a reasonable choice for a different product. For QuoteNode specifically:
- Go — Excellent for network services and CLI tools, but its type system is weaker (no generics until recently, no sum types, limited error modeling). Writing a complex pricing engine with deterministic multi-currency calculations is more error-prone in Go. Additionally, the ecosystem for enterprise concerns (ORM, migrations, PDF rendering, security frameworks) is less mature.
- Rust — Maximizes performance and memory safety, but at the cost of development velocity and ecosystem maturity for business applications. QuoteNode is a CRUD-heavy business platform, not a systems programming project. The compile times and learning curve would slow development without proportional benefit.
- Node.js — Fast to prototype, but single-threaded concurrency, weak type enforcement (even with TypeScript), and a fragmented ecosystem make it a poor fit for a security-critical platform handling financial calculations. The npm dependency tree depth introduces supply chain risk.
What this means for resource consumption
Java applications consume more memory than equivalent Go or Rust services. A QuoteNode backend typically uses 512 MB to 1.5 GB of RAM depending on load and JVM configuration. This is a deliberate trade-off:
- The JVM’s JIT compiler optimizes hot paths after warmup, delivering throughput comparable to natively compiled languages for sustained workloads.
- Garbage collection pauses are negligible for a single-tenant application with typical B2B load patterns (tens of concurrent users, not thousands).
- The memory overhead buys: a rich type system, mature security primitives, battle-tested libraries, and faster feature development.
For a self-hosted, single-tenant application serving one organization, the difference between 256 MB and 1 GB of RAM is operationally irrelevant. The cheapest VPS that can run Docker Compose can run QuoteNode comfortably.
PostgreSQL 16
PostgreSQL is the only supported database. This is intentional:
- ACID compliance — every offer calculation, status transition, and audit log write occurs within a proper transaction. PostgreSQL’s MVCC implementation provides strong isolation guarantees.
- JSON support — offer snapshots are stored as JSONB columns, enabling efficient querying of historical offer data without a separate document store.
- Extensions — pgcrypto for server-side hashing, pg_trgm for full-text search on customer names and products.
- Reliability — PostgreSQL has decades of production history in financial and commercial systems.
- Backup tooling — pg_dump in custom format supports consistent online backups without stopping the application.
Database encryption
Sensitive fields are encrypted at the application layer using AES-256-GCM (authenticated encryption).
Always encrypted (requires DB_ENCRYPTION_KEY):
- TOTP secrets (2FA backup codes)
- SMTP credentials
Optionally encrypted (when ENCRYPT_PII=true):
- Customer PII — names, email, phone, NIP/tax ID
- Contact person PII — names, email, phone
- Supplier PII — tax ID, email, phone
- Supplier contact PII — names, email, phone
- User PII — email, first name, last name, phone number
- Order intent PII — client name, email, phone
When PII encryption is enabled, exact-match queries use HMAC-SHA256 blind indexes (stored alongside the ciphertext) instead of direct column lookups. Full-text search falls back to non-PII fields (company names, cached display names). LIKE-based searches on encrypted columns are not possible by design — this is a deliberate trade-off for data protection.
The encryption key is derived from the DB_ENCRYPTION_KEY environment variable via SHA-256. The key never touches the database — it exists only in application memory. A production configuration validator prevents startup with the default development key. A separate startup validator detects mismatches between the encryption flag and the actual database state (e.g., encrypted data with ENCRYPT_PII=false, or a wrong key) and refuses to start, preventing silent data corruption.
CLI tools are provided for bulk operations: --pii-encrypt (encrypt existing plaintext), --pii-decrypt (decrypt back to plaintext), and --pii-rekey=<old-key> (re-encrypt with a new key).
Encryption at rest for the full database is delegated to the hosting infrastructure (LUKS, encrypted EBS volumes, etc.), as this provides better performance than column-level encryption for bulk data.
Vue 3 Frontend
The frontend is a single-page application built with Vue 3, Vite, PrimeVue 4, and Tailwind CSS 4:
- Vue 3 — reactive, component-based UI with strong TypeScript support and a mature ecosystem.
- PrimeVue 4 — enterprise-grade component library with accessible, feature-rich data tables, forms, and dialogs. This avoids reinventing complex UI patterns.
- Tailwind CSS 4 — utility-first styling with design tokens, enabling consistent visual language without CSS bloat.
- Vite — fast build tooling with hot module replacement for development productivity.
- vue-i18n — full internationalization with English and Polish (additional languages planned).
The frontend communicates with the backend exclusively through a typed REST API. It is served as static assets by Caddy and has no direct database access.
Gotenberg (PDF Engine)
PDF generation uses Gotenberg, a Chromium-based HTML-to-PDF conversion service:
- Renders modern CSS (flexbox, grid, custom properties) accurately.
- Runs as a separate Docker container, isolating the rendering process.
- Supports pagination, custom margins, and header/footer templates.
- No direct Chromium dependency in the backend — communication via HTTP API.
The Gotenberg container consumes approximately 200-400 MB of RAM. It is only active during PDF generation and can be resource-limited via Docker configuration.
Caddy (Reverse Proxy)
Caddy 2 serves as the reverse proxy with automatic HTTPS:
- Automatic TLS certificate provisioning via Let’s Encrypt.
- HTTP/2 and HTTP/3 support.
- Simple, declarative configuration.
- Minimal resource footprint (~20 MB RAM).
In Coolify deployments, Coolify’s own Caddy proxy handles TLS, and the application container does not include a separate proxy.
Total Resource Requirements
| Component | RAM (typical) | Disk | CPU |
|---|---|---|---|
| Backend (Java 25) | 512 MB – 1.5 GB | minimal | 1 core |
| PostgreSQL 16 | 256 MB – 512 MB | 1-10 GB (data) | shared |
| Frontend (static) | ~20 MB | ~50 MB | negligible |
| Gotenberg | 200-400 MB | ~500 MB | burst only |
| Caddy | ~20 MB | minimal | negligible |
| Total | ~1.5 – 2.5 GB | ~2-12 GB | 2 cores |
A 4 GB RAM VPS with 2 cores and 20 GB SSD comfortably runs the complete QuoteNode stack for a team of 10-20 users. This is the equivalent of a ~$10-20/month cloud instance — significantly less than the monthly cost of most SaaS CRM subscriptions.
Ideal for Small and Medium Businesses
QuoteNode is designed for organizations with 1 to 50 users:
- Freelancers and solo consultants who need professional offer documents without paying for enterprise CRM.
- Small agencies (5-15 people) who want centralized customer and offer management with team visibility.
- Mid-size companies (15-50 people) with structured sales operations needing pipeline analytics, role-based access, and audit compliance.
The self-hosted model eliminates monthly per-seat fees while providing full control over data, branding, and security configuration. For organizations that already maintain basic server infrastructure (or use a managed Docker hosting platform like Coolify), deploying QuoteNode adds minimal operational overhead.
Multi-Language Roadmap
QuoteNode currently supports English and Polish in both the application UI and all client-facing surfaces (offers, PDFs, public links, emails).
After the initial public beta, we plan to add:
- German (Deutsch)
- French (Français)
- Spanish (Español)
- Portuguese (Português)
The long-term goal is full coverage of all major European languages. The i18n architecture (vue-i18n with structured key files) is designed to support this expansion without code changes — only translation files need to be added.
Offer PDFs and public link pages can already render in any supported locale independently of the UI language, allowing you to present offers to international clients in their preferred language.