NextReady uses MongoDB as its primary database, providing a flexible and scalable solution for your SaaS application.
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.
NextReady implements a connection pooling pattern to efficiently manage database connections, especially during development with hot reloading.
The database connection is configured in src/lib/mongodb.ts
. This file handles:
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
Never commit your .env.local
file to version control. Make sure it's included in your .gitignore
file.
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({})
// ...
}
NextReady uses Mongoose schemas to define the structure of your data. The models are located in the src/models
directory.
NextReady comes with the following pre-defined models:
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.
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"
}
}
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"
}
}
Here are some common database operations you might need to perform in your NextReady application:
// 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
// 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"
})
// 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" } }
)
// Delete a user by ID
await User.findByIdAndDelete("user_id_here")
// Delete multiple records
await User.deleteMany({ subscriptionStatus: "expired" })
// Find posts and populate author information
const posts = await Post.find({})
.populate("author", "name email") // Only include name and email fields
.sort({ createdAt: -1 })
You can easily extend NextReady's database functionality to fit your specific needs.
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)
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
})
When modifying schemas in a production application, be careful about breaking changes. Consider using migration scripts to update existing data.
Follow these best practices to get the most out of MongoDB with NextReady:
// Add to your schema
UserSchema.index({ email: 1 })
PostSchema.index({ slug: 1 }, { unique: true })
PostSchema.index({ author: 1, createdAt: -1 })
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...
})
Now that you understand how to work with the database in NextReady, you might want to explore: