Database

MongoDB models, schemas, and database configuration

Fastly uses MongoDB with Mongoose ODM. All models are located in src/models/.

Data Model Overview

The application uses three main collections:

CollectionPurpose
userauthsAuthentication credentials and verification tokens
usersUser profile information
usersessionsActive and revoked session records

Relationships

  • Each UserAuth has one User profile (created after email verification)
  • Each UserAuth can have multiple UserSession documents

Model Files

users.ts
user-sessions.ts

UserAuth Model

Stores authentication credentials and verification tokens.

Collection: userauths

File: src/models/users.ts

Schema

Prop

Type

Schema Definition

src/models/users.ts
const userAuthSchema = new mongoose.Schema(
  {
    firstName: { type: String, default: null },
    lastName: { type: String, default: null },
    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      trim: true,
      index: true,
    },
    password: { type: String, default: null },
    isVerified: { type: Boolean, default: false, index: true },
    authMethod: {
      type: String,
      enum: ['email', 'google', 'github', 'facebook'],
      required: true,
    },
    verificationCode: { type: String, default: null },
    verificationCodeExpiresAt: { type: Date, default: null },
    resetPasswordToken: { type: String, default: null },
    resetPasswordTokenExpiresAt: { type: Date, default: null },
  },
  { timestamps: true }
);

User Model

Stores user profile information.

Collection: users

File: src/models/users.ts

Schema

Prop

Type

Location Object

interface Location {
  address: string;
  city: string;
  state: string;
  country: string;
  zipCode: string;
}

Social Accounts Array

interface SocialAccount {
  url: string;
  provider: string;
}

Preferences Object

interface Preferences {
  theme: 'light' | 'dark' | 'system';
  font: 'sans' | 'serif' | 'mono' | 'system';
}

Schema Definition

src/models/users.ts
const userSchema = new mongoose.Schema(
  {
    userAuth: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'UserAuth',
      required: true,
      index: true,
    },
    firstName: { type: String, required: true },
    lastName: { type: String, required: true },
    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      index: true,
    },
    username: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      index: true,
    },
    avatar: { type: String, default: null },
    bio: { type: String, default: null, maxLength: 200 },
    location: {
      address: String,
      city: String,
      state: String,
      country: String,
      zipCode: String,
    },
    socialAccounts: [
      {
        url: String,
        provider: String,
      },
    ],
    hasChangedUsername: { type: Boolean, default: false },
    preferences: {
      theme: {
        type: String,
        enum: ['light', 'dark', 'system'],
        default: 'system',
      },
      font: {
        type: String,
        enum: ['sans', 'serif', 'mono', 'system'],
        default: 'system',
      },
    },
    dob: { type: Date, default: null },
  },
  { timestamps: true }
);

UserSession Model

Tracks active and revoked user sessions.

Collection: usersessions

File: src/models/user-sessions.ts

Schema

Prop

Type

Schema Definition

src/models/user-sessions.ts
const userSessionSchema = new mongoose.Schema({
  userAuth: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'UserAuth',
    required: true,
    index: true,
  },
  sessionId: {
    type: String,
    required: true,
    unique: true,
    index: true,
  },
  authMethod: {
    type: String,
    enum: ['email', 'google', 'github'],
    required: true,
  },
  userAgent: { type: String },
  browser: { type: String },
  os: { type: String },
  device: { type: String },
  ipAddress: { type: String },
  location: { type: String, default: null },
  createdAt: { type: Date, default: Date.now, immutable: true },
  lastActiveAt: { type: Date, default: Date.now },
  revokedAt: { type: Date, default: null, index: true },
});

// Compound index for efficient session queries
userSessionSchema.index({ userAuth: 1, revokedAt: 1 });

Database Indexes

Proper indexing ensures efficient queries:

// UserAuth indexes
{ email: 1 }         // unique
{ isVerified: 1 }

// User indexes
{ email: 1 }         // unique
{ username: 1 }      // unique
{ userAuth: 1 }

// UserSession indexes
{ sessionId: 1 }     // unique
{ userAuth: 1 }
{ revokedAt: 1 }
{ userAuth: 1, revokedAt: 1 }  // compound

Database Connection

File: src/lib/config/db-connect.ts

import mongoose from 'mongoose';

const MONGODB_URI = process.env.MONGODB_URI!;

if (!MONGODB_URI) {
  throw new Error('Please define MONGODB_URI in environment variables');
}

let cached = global.mongoose;

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null };
}

export async function connectDB() {
  if (cached.conn) {
    return cached.conn;
  }

  if (!cached.promise) {
    cached.promise = mongoose.connect(MONGODB_URI, {
      bufferCommands: false,
      connectTimeoutMS: 5000,
      serverSelectionTimeoutMS: 5000,
    });
  }

  cached.conn = await cached.promise;
  return cached.conn;
}

The connection is cached globally to prevent creating multiple connections during development hot reloads.


Common Queries

Find user by email

const userAuth = await UserAuth.findOne({ email: email.toLowerCase() });

Find user with profile

const user = await User.findOne({ userAuth: userAuthId })
  .populate('userAuth', 'authMethod isVerified');

Get active sessions

const sessions = await UserSession.find({
  userAuth: userAuthId,
  revokedAt: null,
}).sort({ lastActiveAt: -1 });

Update session activity

await UserSession.updateOne(
  { userAuth: userAuthId, sessionId, revokedAt: null },
  { $set: { lastActiveAt: new Date() } }
);

Revoke session

await UserSession.updateOne(
  { userAuth: userAuthId, sessionId },
  { $set: { revokedAt: new Date() } }
);

For extending the database schema with custom fields, see the Customization guide.

On this page