PROJECT
Lead Full Stack Developer

Momentum

A mobile-first business development tracking platform built to accelerate enterprise sales cycles through real-time pipeline analytics and velocity-based 'Momentum Charts.' Architected as a Turborepo monorepo with a NestJS backend, React Native/Expo mobile client, and Neon serverless Postgres via Drizzle ORM, the platform delivers type-safe full-stack development with shared TypeScript schemas, role-based access control through Better-Auth, and a high-performance glassmorphism UI featuring 60fps Reanimated animations optimized for mobile sales teams in the field.

Tech Stack

React NativeExpoExpo RouterReact Native ReanimatedNestJSNeonDrizzle ORMBetter-AuthTurborepoZustandTailwind CSSResendTypeScript
Momentum 1
Momentum 2
Momentum 3
Momentum 4
Momentum 5
Momentum 6

Status

Production Ready

Type

Enterprise platform

Architected a full-stack monorepo using Turborepo, sharing TypeScript database schemas (Drizzle ORM), email templates (React Email + Resend), and UI components across the NestJS backend and React Native/Expo mobile client.

Built a high-performance cross-platform mobile application using Expo and React Native Reanimated, maintaining consistent 60fps animations through optimized gesture handlers, memoized component trees, and GPU-accelerated blur effects in a custom glassmorphism UI design system.

Developed an enterprise-grade NestJS backend with modular architecture using Dependency Injection, organizing domain logic into focused modules (StatsModule, OpportunitiesModule, AuthModule) with clear service boundaries.

Designed and implemented a serverless Postgres data layer using Neon and Drizzle ORM, ensuring full TypeScript type safety from database schema definition through API response types to mobile client consumption—eliminating runtime type errors and enabling compile-time validation of database queries.

Integrated Better-Auth for multi-tenant session management with custom Drizzle adapter, implementing Role-Based Access Control (RBAC) with distinct permission boundaries for Founders, Admins, and BD professionals.

Built 'Momentum Charts'—custom data visualization components that track business development velocity, pipeline progression, and activity trends using animated, interactive chart rendering optimized for mobile performance.

Implemented frictionless onboarding flows with the 'Capture → Collaborate → Accelerate' methodology, using step-by-step guided experiences with contextual tooltips and progressive feature disclosure.

Designed role-specific dashboard views with aggregate analytics, smart filtering (date ranges, team members, opportunity stages), and drill-down navigation from summary metrics to individual opportunity details.

Built transactional email workflows using React Email templates rendered server-side and delivered through Resend, handling onboarding sequences, session invitations, and activity notifications.

Implemented global state management using Zustand with persistent storage middleware, enabling seamless offline-to-online state transitions and cross-screen data consistency in the mobile application.

Momentum

Overview

Momentum is a productivity-first business development tracking platform designed to empower sales teams rather than monitor them. The platform's core technical differentiator is its Turborepo monorepo architecture—the NestJS backend, React Native mobile client, and shared packages (database schemas, email templates, UI components) all live in a single repository with shared TypeScript types flowing from the Drizzle ORM schema definition through the API layer to the mobile client, ensuring full-stack type safety without manual type synchronization.

The product reimagines business development tracking by focusing on velocity and momentum rather than static pipeline snapshots. Custom "Momentum Charts" visualize how opportunities are moving through the pipeline over time, helping BD professionals and agency leaders identify acceleration patterns, stalled deals, and winning strategies through data visualization rather than spreadsheet analysis.

The mobile-first approach is deliberate: business development happens in meetings, coffee shops, conference halls, and transit—not at desks. Momentum is built so that logging a new contact, updating an opportunity, or checking pipeline status takes seconds from a phone, not minutes from a laptop.

Business Context: The Vision

In the high-stakes world of business development, legacy CRM tools are often clunky and data-heavy, designed for reporting to management rather than helping salespeople sell. They feel like overhead—time spent updating a CRM is time not spent building relationships and closing deals.

The core problem: Traditional BD tracking tools optimize for managerial visibility at the expense of field usability. Sales professionals resist using tools that feel like surveillance rather than support, leading to incomplete data, stale pipelines, and reports that don't reflect reality.

Momentum was built to answer a different question: "How can we help you win today?"

Stakeholder perspective:

  • BD professionals need a tool that helps them, not monitors them. Logging activities should take seconds. Pipeline visibility should help them prioritize their next action, not justify their existence to management.
  • Agency founders/managers need pipeline transparency without creating a culture of surveillance. They want to see aggregate velocity—how fast opportunities move through stages—not micromanage individual activities.
  • Team leads need collaboration tools that celebrate wins and share insights, not dashboards that rank individuals by activity count.

Constraints that shaped the architecture:

  • Mobile-first means every interaction must be optimized for thumb-driven, five-second interactions. Complex data entry flows are the enemy.
  • The platform must feel premium—glassmorphism UI, smooth animations, polished transitions. A utilitarian interface would undermine the product's positioning as a "delightful" tool that people want to use.
  • Full-stack type safety is essential for a small team iterating rapidly. Type mismatches between the API and mobile client waste time on debugging that should be spent on features.
  • The backend must scale without infrastructure management overhead, favoring serverless and managed services over self-hosted infrastructure.

What I Built

1. Turborepo Monorepo Architecture

  • Shared Package Strategy: Structured the monorepo with clearly separated packages: @momentum/db (Drizzle ORM schema and migrations), @momentum/email (React Email templates), @momentum/ui (shared UI primitives), and @momentum/types (shared TypeScript interfaces). Both the NestJS API and Expo mobile app import from these packages, ensuring a single source of truth for data shapes, validation logic, and visual components.
  • Type Safety Pipeline: The type safety chain flows from Drizzle ORM schema definitions (which are TypeScript-first) → inferred database query return types → NestJS service response types → API endpoint response types → mobile client consumption. A schema change in the @momentum/db package produces compile-time errors in both the API and mobile app if the change isn't handled, catching type mismatches before runtime.
  • Build Orchestration: Turborepo manages build caching and task dependencies—building @momentum/db before @momentum/api (which depends on it), running lint across all packages in parallel, and caching intermediate build artifacts so that unchanged packages don't rebuild.

2. React Native Mobile Application

  • Expo Router Navigation: Built the mobile app using Expo Router with file-based routing, organizing screens by domain (opportunities, dashboard, profile, onboarding) with shared layouts for consistent navigation chrome. Deep linking support allows push notifications to navigate directly to specific opportunity details.
  • Glassmorphism Design System: Designed and implemented a premium visual identity using glassmorphism principles—frosted glass card backgrounds with blur effects (expo-blur), layered transparency, dynamic gradient borders, and subtle shadow depths. The design system includes themed components (GlassCard, GlassButton, GlassInput) that maintain visual consistency while being composable for different content types.
  • 60fps Animation Performance: Leveraged React Native Reanimated for all animations, running them on the native UI thread rather than the JavaScript thread. This ensures smooth 60fps performance for card transitions, chart animations, pull-to-refresh interactions, and page transitions even on mid-range devices. Animations are declarative (defined as worklets) rather than imperative, making them composable and testable.
  • Zustand State Management: Implemented Zustand with persistence middleware for mobile state management. The store architecture separates concerns—opportunity data, dashboard filters, user preferences, and onboarding progress each have independent stores with their own persistence strategies. Zustand's minimal API and hook-based access pattern keep component code clean compared to Redux's boilerplate.

3. NestJS Backend Architecture

  • Modular Domain Design: Organized the backend into focused NestJS modules—AuthModule (Better-Auth integration, session management), OpportunitiesModule (CRUD, stage transitions, activity logging), StatsModule (aggregate analytics, momentum calculations), TeamsModule (user management, role assignments), and EmailModule (Resend integration, template rendering). Each module encapsulates its own services, controllers, and DTOs.
  • Dependency Injection for Service Composition: Leveraged NestJS's DI container to compose services cleanly. The StatsService depends on OpportunitiesService and TeamsService to compute aggregate analytics without duplicating data access logic. This makes services testable in isolation (inject mocks) and composable without tight coupling.
  • API Documentation: Built self-documenting API endpoints using NestJS decorators and Swagger integration, generating OpenAPI specs that the mobile client can reference. Request/response DTOs are validated with class-validator and class-transformer, ensuring that invalid payloads are rejected before reaching service logic.

4. Serverless Database Layer

  • Neon Serverless Postgres: Chose Neon for its serverless Postgres offering—the database scales to zero when inactive and auto-scales under load, eliminating the need for capacity planning and infrastructure management. Connection pooling is handled by Neon's proxy layer, which is critical for serverless backends that don't maintain persistent connection pools.
  • Drizzle ORM Schema Design: Defined the entire database schema in TypeScript using Drizzle ORM, which provides SQL-like query building with full type inference. The schema models core entities: users, teams, opportunities (with stage history), activities, and invitation tokens. Relations are defined explicitly, and Drizzle infers the TypeScript types from the schema definition.
  • Migration Strategy: Database migrations are generated from Drizzle schema changes (drizzle-kit generate) and applied through the CI pipeline. This ensures that schema changes are version-controlled, reviewable in PRs, and applied consistently across environments.

5. Authentication & Multi-Tenant RBAC

  • Better-Auth Integration: Integrated Better-Auth as the authentication provider with a custom Drizzle adapter that stores session and account data in the Neon Postgres database (not in an external auth provider's storage). This "own your data" approach means authentication data is co-located with application data, simplifying queries that need to join user profiles with auth metadata.
  • Role-Based Access Control: Implemented three-tier RBAC—Founders (full platform control, team management, billing), Admins (team management, all opportunity access, analytics), and Members (own opportunities, team dashboard, limited analytics). Permissions are checked at the NestJS guard level, ensuring that API endpoints enforce role boundaries regardless of what the mobile client sends.
  • Invitation System: Built a team invitation flow—Founders/Admins generate invite links that encode the target role. New users who follow the link are automatically associated with the team at the specified role. Invitations are tracked in the database with expiration and usage limits.

6. Transactional Email System

  • React Email Templates: Built email templates as React components using the React Email library, stored in the shared @momentum/email package. Templates include onboarding welcome sequences, team invitation emails, activity notifications, and weekly pipeline summaries. Because they're React components, they can be previewed in Storybook during development.
  • Resend Integration: Resend handles email delivery from NestJS Cloud Functions, providing deliverability tracking, bounce handling, and reputation management. The EmailModule in NestJS abstracts the Resend API behind a service interface, making it straightforward to add new email types or switch providers.

Architecture Highlights

Turborepo Shared Package Architecture

The monorepo structure is the architectural backbone that enables rapid, type-safe iteration:

apps/
  api/          → NestJS backend
  mobile/       → Expo React Native app
packages/
  db/           → Drizzle ORM schema, migrations, query helpers
  email/        → React Email templates
  ui/           → Shared UI components
  types/        → Shared TypeScript interfaces

The key insight is that shared packages aren't just about code reuse—they're about single source of truth. The @momentum/db package defines what an Opportunity looks like at the database level. The NestJS API imports this definition to type its query results. The mobile app imports the same types to type its API response handling. If someone changes the Opportunity schema, TypeScript catches every consumer that needs updating.

Why Turborepo over Nx: Turborepo provides the build caching and task orchestration needed without the configuration overhead of Nx. For a project with two apps and four shared packages, Turborepo's simplicity and speed are the right fit. Nx's advanced features (computation caching across CI, affected commands for large monorepos) aren't needed at this scale.

Glassmorphism as Performance Challenge

The glassmorphism design system looks premium but creates real performance challenges on mobile:

  • Blur effects are computationally expensive on mobile GPUs. The implementation uses expo-blur with carefully managed blur radii and only applies blur to card backgrounds (not full-screen overlays) to keep GPU usage manageable.
  • Transparency layers require the rendering engine to composite multiple semi-transparent surfaces. The design system limits blur depth (maximum two overlapping glass surfaces at any point) to prevent rendering slowdown.
  • Dynamic gradients for card borders and accent highlights use pre-computed gradient textures rather than real-time gradient calculation.

These constraints are invisible to the user—the UI looks and feels premium—but they represent deliberate performance trade-offs made during design system development.

Drizzle ORM: TypeScript-First Database Access

Drizzle ORM was chosen over Prisma for several architectural reasons:

  • SQL-like query building: Drizzle's query syntax mirrors SQL, making complex queries (JOINs, aggregations, window functions for momentum calculations) straightforward to write and reason about, unlike Prisma's abstraction layer that hides the SQL.
  • Zero runtime overhead: Drizzle generates SQL at build time with no runtime query engine, resulting in faster query execution and smaller bundle size.
  • Schema as TypeScript: The schema definition is pure TypeScript that exports inferred types, making it a natural fit for the monorepo's shared type system.

Better-Auth: Own Your Auth Data

Better-Auth was chosen over Clerk or Auth0 specifically because it stores authentication data in the application's own database (Neon Postgres) rather than in an external provider's system. This means:

  • Join queries: User profiles, sessions, and auth metadata can be joined with application data in a single query, avoiding the API roundtrip overhead of external auth providers.
  • Data ownership: Authentication data isn't subject to external provider pricing changes, data retention policies, or service outages.
  • Custom session logic: Session management can be customized at the database level—custom session expiry rules, concurrent session limits, device tracking—without being constrained by a provider's API.

The trade-off is maintenance responsibility—session security, token rotation, and vulnerability patching are now internal concerns rather than delegated to a managed provider.

Technical Deep Dive: Turborepo Shared Package Architecture with Drizzle Schema Sharing

The most architecturally significant aspect of Momentum is how the Turborepo monorepo structure, combined with Drizzle ORM's TypeScript-first schema definitions, creates end-to-end type safety across the entire stack.

The Problem: In traditional full-stack development, the backend defines database schemas and API contracts, and the frontend defines its own types that hopefully match. Over time, these diverge—the backend adds a field that the frontend doesn't know about, or the frontend expects a field that the backend renamed. These mismatches surface as runtime errors in production, not compile-time errors during development.

The Architecture:

  1. Schema as Source of Truth (@momentum/db): The Drizzle ORM schema file defines database tables as TypeScript objects. Drizzle infers TypeScript types from these definitions—InsertOpportunity (the type for creating a new record), SelectOpportunity (the type for a queried record), and relation types. These inferred types are exported from the @momentum/db package.

  2. API Consumes Schema Types (apps/api): The NestJS API imports @momentum/db directly. Service methods return SelectOpportunity (or derived types) without redeclaring the shape. DTOs (Data Transfer Objects) for API endpoints are built from the same schema types, ensuring that request validation matches the database's expectations.

  3. Mobile Consumes the Same Types (apps/mobile): The Expo mobile app imports @momentum/types (which re-exports relevant types from @momentum/db). API response handling uses these types to parse server responses, and UI components receive typed props that match the database schema. A typo in a field name is caught by TypeScript immediately.

  4. Change Propagation: When the schema changes (e.g., adding a priority field to opportunities), the flow is:

    • Modify the Drizzle schema in @momentum/db → generate migration
    • Turborepo detects the change → rebuilds dependent packages
    • The NestJS API shows compile errors where priority isn't handled → fix the API
    • The mobile app shows compile errors where priority isn't displayed or handled → fix the mobile app
    • All three changes are committed together in one PR

    This is the key value: schema changes are atomic across the stack. It's impossible to merge a database migration without also updating the API and mobile app to handle it.

  5. Shared Email Templates: The @momentum/email package uses React Email to define email templates as React components. The NestJS API's EmailModule imports these components and renders them server-side with data before sending through Resend. Because the templates are TypeScript React components, they have typed props—the API can't render an email template with incorrect data without a compile error.

Why this matters: Type safety across the full stack eliminates an entire class of bugs—the "the API returns X but the client expects Y" bugs that are notoriously difficult to debug because they surface far from their cause. In a small team iterating rapidly on both the API and mobile app, this prevents the slow accumulation of subtle mismatches that larger teams manage through API documentation and contract testing.

Key Challenges & Solutions

  • Challenge: Glassmorphism blur effects on mobile caused frame drops below 60fps on mid-range Android devices, degrading the premium feel the design system intended to create. Approach: Limited blur application to card-level backgrounds only (not full-screen overlays), capped blur radius at values the mobile GPU handles efficiently, and set a maximum of two overlapping glass surfaces in any single view. Pre-computed gradient textures replace real-time gradient calculation for border effects. Why: The visual premium of glassmorphism only works if it's smooth. A beautiful glass card that janks during scroll is worse than a simple flat card that scrolls at 60fps. The performance constraints are design constraints—they define what's achievable, not what's ideal.

  • Challenge: Neon serverless Postgres drops connections during cold starts, causing intermittent connection errors in the NestJS API when the database scales from zero. Approach: Configured Neon's connection pooling proxy (which maintains warm connections even when compute scales to zero) and implemented connection retry logic with exponential backoff in the Drizzle client configuration. Health check endpoints warm the database connection before handling user requests. Why: Serverless databases trade consistent connection availability for cost efficiency and scaling simplicity. The retry logic handles the cold start latency transparently, and the connection pooling proxy reduces cold start frequency.

  • Challenge: Better-Auth's Drizzle adapter needed custom implementation since the official adapter didn't support Neon's serverless connection pooling configuration. Approach: Built a custom Drizzle adapter for Better-Auth that uses Neon's @neondatabase/serverless driver with WebSocket connections for session operations. The adapter implements Better-Auth's storage interface while handling Neon-specific connection lifecycle. Why: Better-Auth's value (owning auth data in your own database) requires the adapter to work with your specific database setup. A custom adapter is a one-time investment that enables the "own your data" architecture throughout the application's lifetime.

  • Challenge: Shared monorepo packages needed to work in both Node.js (NestJS) and React Native (Expo) environments, which have different module resolution, API availability, and bundling requirements. Approach: Structured shared packages with platform-agnostic code only—pure TypeScript types, Drizzle schemas, and React components without Node.js or React Native-specific APIs. Platform-specific code (file system access, native modules) stays in the respective app packages. Package.json exports use conditional exports to provide appropriate entry points for each consumer. Why: Shared packages that contain platform-specific code create bundling nightmares—Node.js crypto modules appearing in the mobile bundle, React Native's metro bundler choking on Node.js imports. Strict platform-agnosticism in shared packages prevents these issues entirely.

Outcomes

  • Full-Stack Type Safety: The Turborepo + Drizzle ORM architecture provides compile-time type checking from database schema through API to mobile client, catching data shape mismatches before they reach runtime.
  • Premium Mobile Experience: The glassmorphism design system with Reanimated animations delivers a visually distinctive, 60fps mobile experience that differentiates the product from utilitarian CRM tools.
  • Rapid Iteration: Shared packages in the monorepo reduce duplication and ensure consistency, enabling simultaneous API and mobile feature development with confidence that changes are compatible.
  • Scalable Infrastructure: The Neon serverless Postgres + NestJS combination provides production-ready infrastructure that scales on demand without capacity planning, suitable for early-stage growth through significant user base expansion.
  • Maintainable Architecture: NestJS's modular design with Dependency Injection keeps backend services focused and testable, while Zustand's minimal API keeps mobile state management clean.

Engineering Takeaways

Building Momentum reinforced the value of monorepo architectures in full-stack mobile development. The ability to share the Drizzle schema and React Email templates across the stack maintained strict type safety and design consistency while iterating rapidly. It also highlighted how a "mobile-first" approach in enterprise software can significantly improve user engagement compared to legacy desktop-centric tools—when the tool is faster to use than not using it, adoption follows naturally.

Patterns I'd reuse:

  • Turborepo with shared TypeScript packages for any project where a single team owns both the API and the client. The type safety benefits compound over time as the schema evolves.
  • Drizzle ORM over Prisma for projects that need complex SQL queries (aggregations, window functions, CTEs). Drizzle's SQL-like syntax makes these straightforward, while Prisma's abstraction layer fights against them.
  • Better-Auth with a custom adapter for projects that want to own their authentication data. The initial setup cost is higher than Clerk/Auth0, but the long-term flexibility and data ownership are worth it.

What I'd reconsider:

  • The glassmorphism design system, while visually striking, required significant performance tuning for mobile. A future iteration might use glassmorphism more selectively (hero cards, key interactions) rather than as the pervasive design language, reducing the performance constraint surface.
  • Zustand's simplicity is excellent for the current app size, but as the feature set grows, the lack of built-in dev tools (comparable to Redux DevTools) makes debugging complex state flows more difficult. A migration path to Redux Toolkit might be warranted at scale.

Trade-offs acknowledged:

  • Chose Turborepo over separate repositories, accepting monorepo tooling complexity (workspace management, shared dependency versions) in exchange for atomic cross-stack changes and shared type safety.
  • Chose Neon serverless Postgres over self-hosted PostgreSQL, accepting cold start latency and connection pooling complexity in exchange for zero infrastructure management and automatic scaling.
  • Chose Better-Auth over Clerk/Auth0, accepting the maintenance burden of self-hosted auth (session security, token rotation) in exchange for data ownership and co-located auth data that can be queried alongside application data.
  • Chose React Native Reanimated for all animations over simpler Animated API, accepting the worklet learning curve in exchange for guaranteed 60fps native-thread animations that the JS-thread Animated API can't consistently deliver.