Every engineering team claims to build for scale. Fewer actually define what that means for their product, their customers, and their team. At Genfinish, we’ve been deliberate about codifying the principles that guide how we build Cognaxa — because enterprise software isn’t just about what you build, it’s about how reliably it works when your most important customer has their most important exam.
Here’s our playbook.
Principle 1: Choose Boring Technology
When we started building Cognaxa, the temptation was strong: use the latest framework, the newest database, the most exciting deployment platform. Instead, we chose deliberately:
- PostgreSQL over newer databases — because it has 30 years of battle-testing, RLS for multi-tenancy, and we’ll never outgrow it
- Express.js over newer Node frameworks — because its middleware model is understood by every backend developer on the planet
- Redis for sessions and caching — because it’s the industry standard with predictable performance characteristics
- React for the frontend — because the ecosystem, hiring pool, and tooling are unmatched
“Boring technology” doesn’t mean bad technology. It means technology where the failure modes are well-documented, the community is large, and the edge cases have been discovered by someone else.
This isn’t about avoiding innovation. It’s about choosing where to innovate. We innovate in our product — AI grading, interactive assessments, proctoring algorithms — not in our infrastructure. Nobody ever lost a customer because they used PostgreSQL instead of the database of the month.
Principle 2: Vertical Integration Over Microservices
For a team of our size, microservices would be premature optimization at best and an operational nightmare at worst. Cognaxa is a modular monolith:
One Express server
├── Auth module (routes + middleware)
├── Course module (routes + validation)
├── Quiz module (routes + grading logic)
├── Assessment module (routes + analytics)
├── Interactive Quiz module (language assessments)
├── Media module (S3 integration)
└── Shared services (cache, storage, DB)
Each module has clear boundaries — its own routes, validation schemas, and business logic — but they share a single database connection pool, a single Redis instance, and a single deployment unit.
The benefits:
- One deployment — deploy once, everything updates
- Shared types — TypeScript interfaces are shared across modules without API contracts
- Simple debugging — one log stream, one process, one tracing context
- Easy refactoring — moving code between modules is a file move, not a service migration
We’ll extract services when (and only when) a specific module has fundamentally different scaling requirements. Until then, the modular monolith serves us well.
Principle 3: Security Is Architecture, Not a Feature
We’ve written extensively about our zero-trust architecture, but the principle bears repeating: security decisions are made at the architectural level, not bolted on afterwards.
This means:
- RLS is not optional — every table with tenant data gets Row-Level Security policies before any application code is written
- Auth middleware is global — the default is authenticated. Public routes are explicitly marked, not the other way around
- Validation is at the boundary — every API endpoint validates input with Zod schemas before any business logic executes
// The pattern: validate → authenticate → authorize → execute
router.post(
'/courses/:courseId/quizzes',
requireAuth, // Authentication
requireRoles('teacher', 'admin'), // Authorization
validate(createQuizSchema), // Input validation
createQuizHandler // Business logic
);
Principle 4: Own Your Data Model
Our database schema is designed for the long term. Every table has:
- UUID primary keys — globally unique, no collision across tenants
- tenant_id — every row is scoped to a tenant
- created_at / updated_at timestamps — audit trail by default
- Explicit ordering —
order_indexcolumns instead of relying on insertion order
We resist the temptation to store complex data as untyped JSON. When data has structure, it gets a table. The exception is truly dynamic data (quiz responses, proctoring logs) where the schema varies per question type — those use JSONB, but with application-level type validation.
Principle 5: Technical Debt Is a Budget, Not a Backlog
Every codebase has technical debt. The mistake is treating it as something to eliminate rather than something to manage. Our approach:
- Document it intentionally: When we take a shortcut, we leave a clear
// TODO(debt):comment explaining what the ideal solution would be and why we deferred it - Budget time for it: 20% of each sprint is allocated to debt reduction — no negotiation
- Measure it: We track our debt items in a living document, categorized by risk level and effort
- Prioritize by blast radius: A debt item that could cause data loss is addressed immediately. A debt item that makes a developer’s life slightly harder can wait
The goal isn’t zero debt — it’s managed debt at a level that doesn’t slow down feature delivery or compromise reliability.
Principle 6: Build for Observability
You can’t fix what you can’t see. Every Cognaxa deployment includes:
- Structured logging: JSON-formatted logs with request IDs, tenant context, and timing data
- Health checks: A
/healthendpoint that validates database, Redis, and S3 connectivity - Performance monitoring: Request duration tracking at the middleware level
- Error boundaries: Global error handlers that log context without exposing internals to clients
// Global error handler — log everything, expose nothing
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error({
error: err.message,
stack: err.stack,
path: req.path,
method: req.method,
tenantId: req.user?.tenantId,
requestId: req.headers['x-request-id'],
});
res.status(500).json({
error: 'Internal server error',
requestId: req.headers['x-request-id'],
});
});
The Enterprise Readiness Checklist
When we say “enterprise-ready,” we mean we can answer “yes” to all of these:
- ✅ Can one customer’s load affect another customer’s performance? (No — RLS + tenant-scoped caching)
- ✅ Can we revoke a session instantly? (Yes — server-side Redis sessions)
- ✅ Can we audit who did what and when? (Yes — structured logging with tenant context)
- ✅ Can we deploy without downtime? (Yes — rolling deployments on Vercel/Railway)
- ✅ Can we restore data to a point in time? (Yes — daily PostgreSQL backups with PITR)
- ✅ Can we meet a 99.9% uptime SLA? (Yes — and we do)
Looking Forward
These principles aren’t rules carved in stone. They evolve as our product grows, our team scales, and our customers push us into new territory. What doesn’t change is our commitment to building software that enterprises can depend on — not just today, but for the years their students will use it.
Want to learn more about our engineering culture? We’re always looking for engineers who think about these things. Get in touch.