Post 02

Session 3: The Big Pivot

Rebuilding the entire backend in 5.5 hours with Node.js, TypeScript, and Prisma

Nov 26, 2025 12 min read Architecture, Node.js, Prisma
BREAKING CHANGE

From: Laravel 11 + PHP + SQLite
To: Node.js + Express + TypeScript + MySQL + Prisma

Why Node.js?

The decision to pivot wasn't about Node.js being "better" than Laravel. It was about choosing the right tool for our specific requirements. Here's why Node.js won:

🔐
Encryption First

Node.js crypto module gives us low-level control over encryption. No framework magic to fight against.

📝
TypeScript

Type safety across the entire stack. Catches encryption bugs at compile time. Same language as our Next.js frontend.

🗄️
Prisma ORM

Multi-database support out of the box. Separate clients for platform vs vault. Type-safe queries.

Async Native

Encryption/decryption on every request benefits from non-blocking I/O. Promise-based APIs everywhere.

The New Architecture

Dual Database Pattern

The most important architectural decision was separating data into two databases:

┌─────────────────────┐    ┌─────────────────────┐
│   timos_platform    │    │    timos_vault      │
├─────────────────────┤    ├─────────────────────┤
│ • users             │    │ • daily_entries     │
│ • auth_sessions     │    │ • feels_entries     │
│ • oauth_accounts    │    │ • pillars           │
│ • subscriptions     │    │ • weekly_recaps     │
│ • user_deks         │    │ • morning_habits    │
│ • audit_logs        │    │ ALL ENCRYPTED       │
└─────────────────────┘    └─────────────────────┘

Why Two Databases?
The platform database contains authentication and billing data that needs to be queryable. The vault database contains user content that's always encrypted. This separation means:

  • We can backup/restore them independently
  • Different retention policies
  • Vault can be hosted in a different region for compliance
  • Clear security boundary

Project Structure

api/
├── src/
│   ├── index.ts           # Express entry point
│   ├── middleware/
│   │   ├── auth.ts        # JWT verification
│   │   ├── rateLimit.ts   # Rate limiting
│   │   └── csrf.ts        # CSRF protection
│   ├── services/
│   │   ├── auth.ts        # Signup, login, MFA
│   │   ├── oauth.ts       # Microsoft, Google, Apple
│   │   ├── encryption.ts  # MEK/DEK management
│   │   ├── pillars.ts     # Pillar CRUD
│   │   ├── dailyEntries.ts
│   │   ├── feelsEntries.ts
│   │   └── ...
│   ├── routes/
│   │   ├── auth.ts
│   │   ├── pillars.ts
│   │   └── ...
│   └── prisma/
│       ├── platform.prisma
│       └── vault.prisma
└── package.json

Building in 5.5 Hours

Here's how we structured the sprint:

Hour 1: Foundation
  • Express + TypeScript setup
  • Prisma installation and configuration
  • Dual database connections
  • Basic middleware (CORS, helmet, body-parser)
Hour 2: Database Schema
  • Platform schema: 11 tables (users, sessions, tokens, DEKs, etc.)
  • Vault schema: 9 tables (entries, pillars, recaps, etc.)
  • Prisma migrations
  • Type generation
Hours 3-4: Core Services
  • Encryption service (MEK/DEK)
  • Auth service (signup, login, JWT)
  • MFA service (TOTP with encrypted secrets)
  • OAuth service (Microsoft, Google, Apple stubs)
Hour 5: Routes & API
  • Auth routes (/signup, /login, /verify-mfa)
  • OAuth callback routes
  • Protected route middleware
  • Error handling
Hour 5.5: Testing & Fixes
  • Manual API testing
  • Bug fixes
  • Frontend API client updates
  • Documentation
Session 3 Stats

Time spent: 5.5 hours
Manual estimate: 25.0 hours
ROI: 4.5x
Files created: ~35
Lines of code: ~4,000

What Made This Possible

Rebuilding an entire backend in 5.5 hours sounds impossible. Here's what made it work:

  1. Clear Requirements: Sessions 1-2 taught us exactly what we needed. No exploratory design work - we knew the data model, the auth flow, the API shape.
  2. AI Pair Programming: Claude Code generated boilerplate, caught type errors, and suggested patterns. We focused on architecture decisions.
  3. TypeScript + Prisma: Type safety meant fewer bugs. Prisma's schema language made database design declarative.
  4. No Perfectionism: We built the minimum viable backend. Polish would come later. Working code beats perfect code.

Key Takeaways

  • Choose architecture based on your hardest requirements
  • Dual databases provide clear security boundaries
  • TypeScript + Prisma is a powerful combination
  • 5.5 hours of focused work can accomplish a lot with clear requirements
  • Previous "failed" work provides valuable learning