NextReady

Database Configuration

NextReady uses MongoDB as its primary database, providing a flexible and scalable solution for your SaaS application.

Overview

NextReady uses MongoDB directly (without an ORM like Prisma) for data storage, with Mongoose as the modeling tool. This approach provides flexibility while maintaining structure through schemas.

Key Features

  • MongoDB for flexible document storage
  • Mongoose for schema validation and modeling
  • Connection pooling for optimal performance
  • TypeScript interfaces for type safety

Database Connection

NextReady implements a connection pooling pattern to efficiently manage database connections, especially during development with hot reloading.

Connection Configuration

The database connection is configured in src/lib/mongodb.ts. This file handles:

  • Establishing the connection to MongoDB
  • Caching the connection to prevent multiple connections during development
  • Error handling for connection failures

Environment Variables

First, set up your MongoDB connection string in your environment variables:

MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/your-database?retryWrites=true&w=majority

Security Note

Never commit your .env.local file to version control. Make sure it's included in your .gitignore file.

Using the Database Connection

To use the database connection in your code, import the dbConnect function:

import dbConnect from "@/lib/mongodb"

async function myDatabaseFunction() {
  // Connect to the database
  await dbConnect()
  
  // Now you can use Mongoose models
  const users = await User.find({})
  // ...
}

Data Models

NextReady uses Mongoose schemas to define the structure of your data. The models are located in the src/models directory.

Core Models

NextReady comes with the following pre-defined models:

User Model

The User model (src/models/User.ts) defines the structure for user accounts:

// User model fields
{
  name: String,
  email: {
    type: String,
    unique: true,
    required: true
  },
  image: String,
  emailVerified: {
    type: Date,
    default: null
  },
  password: String,
  // Additional fields from OAuth providers are allowed
}

The User model includes methods for password hashing and verification, and automatically removes the password field when converting to JSON.

Post Model

The Post model (src/models/Post.ts) is used for the blog functionality:

// Post model fields
{
  title: {
    type: String,
    required: true
  },
  slug: {
    type: String,
    required: true,
    unique: true
  },
  content: {
    type: String,
    required: true
  },
  excerpt: String,
  coverImage: String,
  published: {
    type: Boolean,
    default: false
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "User"
  }
}

Contact Model

The Contact model (src/models/Contact.ts) stores contact form submissions:

// Contact model fields
{
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  message: {
    type: String,
    required: true
  },
  status: {
    type: String,
    enum: ["new", "read", "replied"],
    default: "new"
  }
}

Common Database Queries

Here are some common database operations you might need to perform in your NextReady application:

Finding Records

// Find all users
const users = await User.find({})

// Find a user by email
const user = await User.findOne({ email: "user@example.com" })

// Find a user by ID
const user = await User.findById("user_id_here")

// Find with projection (only return specific fields)
const users = await User.find({}, "name email")

// Find with conditions
const premiumUsers = await User.find({ subscriptionStatus: "active" })

// Find with sorting
const sortedUsers = await User.find({}).sort({ createdAt: -1 }) // Newest first

Creating Records

// Create a new user
const newUser = new User({
  name: "John Doe",
  email: "john@example.com",
  password: "securepassword"
})
await newUser.save()

// Alternative method
const newUser = await User.create({
  name: "John Doe",
  email: "john@example.com",
  password: "securepassword"
})

Updating Records

// Update a user by ID
await User.findByIdAndUpdate("user_id_here", { name: "New Name" })

// Update with options (return the updated document)
const updatedUser = await User.findByIdAndUpdate(
  "user_id_here",
  { name: "New Name" },
  { new: true } // Return the updated document
)

// Update multiple records
await User.updateMany(
  { subscriptionStatus: "trial" },
  { $set: { subscriptionStatus: "expired" } }
)

Deleting Records

// Delete a user by ID
await User.findByIdAndDelete("user_id_here")

// Delete multiple records
await User.deleteMany({ subscriptionStatus: "expired" })

Populating References

// Find posts and populate author information
const posts = await Post.find({})
  .populate("author", "name email") // Only include name and email fields
  .sort({ createdAt: -1 })

Customizing the Database

You can easily extend NextReady's database functionality to fit your specific needs.

Creating a New Model

To create a new model, add a new file in the src/models directory:

// src/models/Product.ts
import mongoose from "mongoose"

export interface IProduct {
  name: string
  description: string
  price: number
  imageUrl?: string
  inStock: boolean
}

const ProductSchema = new mongoose.Schema<IProduct>({
  name: {
    type: String,
    required: true
  },
  description: {
    type: String,
    required: true
  },
  price: {
    type: Number,
    required: true
  },
  imageUrl: String,
  inStock: {
    type: Boolean,
    default: true
  }
}, {
  timestamps: true
})

export default mongoose.models.Product || mongoose.model<IProduct>("Product", ProductSchema)

Extending Existing Models

To add fields to an existing model, modify the interface and schema in the model file. For example, to add a role field to the User model:

// In src/models/User.ts
export interface IUser {
  email: string
  name: string
  image?: string
  emailVerified?: Date | null
  password?: string
  role?: string // Added role field
}

const UserSchema = new mongoose.Schema<IUser>({
  // Existing fields...
  role: {
    type: String,
    enum: ["user", "admin", "editor"],
    default: "user"
  }
}, {
  timestamps: true,
  strict: false
})

Schema Modification Warning

When modifying schemas in a production application, be careful about breaking changes. Consider using migration scripts to update existing data.

Best Practices

Follow these best practices to get the most out of MongoDB with NextReady:

Performance Optimization

  • Create indexes for frequently queried fields:
    // Add to your schema
    UserSchema.index({ email: 1 })
    PostSchema.index({ slug: 1 }, { unique: true })
    PostSchema.index({ author: 1, createdAt: -1 })
  • Use projection to limit returned fields when you don't need the entire document
  • Limit batch sizes when dealing with large collections

Security Considerations

  • Always validate user input before storing it in the database
  • Use environment variables for database credentials
  • Set up proper database access controls in MongoDB Atlas
  • Implement proper error handling to prevent information leakage

Data Validation

Use Mongoose's built-in validation features to ensure data integrity:

const ProductSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, "Product name is required"],
    trim: true,
    maxlength: [100, "Name cannot be more than 100 characters"]
  },
  price: {
    type: Number,
    required: [true, "Price is required"],
    min: [0, "Price must be a positive number"]
  },
  // Other fields...
})

Next Steps

Now that you understand how to work with the database in NextReady, you might want to explore: