Skip to Content
ServerModelsDatabase Models Overview

Database Models Overview

Fast Forward Dev uses a three-model architecture for clean separation of concerns.

Model Architecture

Three Models

1. User Model

Core user profile information.

Fields:

  • displayName - User’s display name
  • primaryEmail - Main email (informational)
  • isAdmin - Admin flag
  • createdAt - Account creation date
  • updatedAt - Last update date

Location: src/models/user.model.ts

2. Identity Model

Authentication providers and credentials.

Fields:

  • userId - Reference to User
  • provider - LOCAL, GOOGLE, GITHUB, etc.
  • providerAccountId - Unique ID from provider
  • email - Email for this identity
  • emailVerified - Verification status
  • passwordHash - For local auth only
  • salt - For local auth only
  • providerData - OAuth provider data

Location: src/models/identity.model.ts

3. Billing Model

Payment and subscription information.

Fields:

  • userId - Reference to User (optional until signup)
  • email - For pre-purchase lookup
  • stripeCustomerId - Stripe customer ID
  • currentPlan - ‘basic’, ‘pro’, or null
  • oneTimePurchases - Array of purchases
  • subscriptions - Array of subscriptions

Location: src/models/billing.model.ts

Why Three Models?

Benefits

Separation of Concerns

  • User profile separate from auth
  • Auth separate from billing
  • Each model has single responsibility

Multiple Auth Providers

  • One user can have multiple identities
  • Email/password + Google OAuth
  • Easy to add new providers

Pre-Purchase Support

  • Billing records created before signup
  • Users purchase then create account
  • Billing links to user after signup

Flexibility

  • Change auth without affecting billing
  • Add providers without schema changes
  • Clean data organization

Relationships

User → Identity (One to Many)

One user can have multiple identities:

// Find all identities for a user const identities = await Identity.find({ userId: user._id }) // User might have: // - Local identity (email/password) // - Google identity (OAuth) // - GitHub identity (OAuth)

User → Billing (One to One)

One user has one billing record:

// Find billing for a user const billing = await Billing.findOne({ userId: user._id })

Billing → User (Optional)

Billing can exist without a user (pre-purchase):

// Billing created during Stripe checkout const billing = new Billing({ userId: null, // No user yet email: 'john@example.com', stripeCustomerId: 'cus_123', currentPlan: 'pro' }) // Later, during signup, link to user billing.userId = user._id await billing.save()

Indexes

All models have optimized indexes for performance:

User Indexes

userSchema.index({ primaryEmail: 1 })

Identity Indexes

identitySchema.index({ userId: 1 }) identitySchema.index({ provider: 1, providerAccountId: 1 }, { unique: true }) identitySchema.index({ email: 1 })

Billing Indexes

billingSchema.index({ userId: 1 }, { unique: true, sparse: true }) billingSchema.index({ stripeCustomerId: 1 }, { unique: true }) billingSchema.index({ email: 1 })

Common Queries

Get User with All Data

const user = await User.findById(userId) const identities = await Identity.find({ userId }) const billing = await Billing.findOne({ userId }) return { user, identities, billing }

Find User by Email

// Find identity first const identity = await Identity.findOne({ email: email.toLowerCase(), provider: IdentityProvider.LOCAL }) if (identity) { const user = await User.findById(identity.userId) }

Check User’s Plan

const billing = await Billing.findOne({ userId }) const plan = billing?.currentPlan // 'basic', 'pro', or null

Next Steps

Last updated on