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 nameprimaryEmail- Main email (informational)isAdmin- Admin flagcreatedAt- Account creation dateupdatedAt- Last update date
Location: src/models/user.model.ts
2. Identity Model
Authentication providers and credentials.
Fields:
userId- Reference to Userprovider- LOCAL, GOOGLE, GITHUB, etc.providerAccountId- Unique ID from provideremail- Email for this identityemailVerified- Verification statuspasswordHash- For local auth onlysalt- For local auth onlyproviderData- 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 lookupstripeCustomerId- Stripe customer IDcurrentPlan- ‘basic’, ‘pro’, or nulloneTimePurchases- Array of purchasessubscriptions- 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 nullNext Steps
- User Model - User schema details
- Identity Model - Auth providers
- Billing Model - Payment data
- API Routes - Using models in routes
Last updated on