Founderflow Logo

Database Integration

Complete guide to MongoDB integration with Mongoose ODM. Learn how to set up, configure, and use the database in your Founderflow Boilerplate application.

Overview

MongoDB + Mongoose

The boilerplate uses MongoDB as the primary database with Mongoose ODM for schema validation, type safety, and easy data manipulation. This combination provides flexibility and scalability for modern applications.

Schema Validation

Automatic data validation and type checking

TypeScript Support

Full type safety with Mongoose schemas

Connection Pooling

Optimized database connections for performance

Middleware Support

Pre/post hooks for data processing

Indexing

Automatic index creation for optimal queries

Error Handling

Comprehensive error handling and logging

Setup & Configuration

Environment Variables

Configure your MongoDB connection using environment variables:

.env.local
# MongoDB Connection
MONGO_URI=mongodb://localhost:27017/your-database
# Or for MongoDB Atlas:
MONGO_URI=mongodb+srv://username:[email protected]/database

Database Connection

The database connection is handled in the lib/mongodb.ts file:

lib/mongodb.ts
import mongoose from 'mongoose';
const MONGODB_URI = process.env.MONGO_URI;
if (!MONGODB_URI) {
throw new Error('Please define the MONGO_URI environment variable');
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function dbConnect() {
if (cached.conn) return cached.conn;
if (!cached.promise) {
cached.promise = mongoose.connect(MONGODB_URI);
}
cached.conn = await cached.promise;
return cached.conn;
}
export default dbConnect;

Database Models

User Model

The main user model with authentication and subscription data:

models/User.ts
import mongoose, { Schema, Document } from 'mongoose';
interface IUser extends Document {
id: string;
emailAddress: string[];
firstName: string;
lastName: string;
picture: string;
stripeCustomerId?: string;
lemonsqueezyCustomerId?: string;
credits: number;
plan: {
id: string;
type: string;
name: string;
price: string;
isPremium: boolean;
};
}
const UserSchema = new Schema({
id: { type: String, required: true, unique: true },
emailAddress: [String],
firstName: String,
lastName: String,
picture: String,
stripeCustomerId: String,
lemonsqueezyCustomerId: String,
credits: { type: Number, default: 0 },
plan: {
id: String,
type: String,
name: String,
price: String,
isPremium: Boolean
}
}, { timestamps: true });
export default mongoose.models.User || mongoose.model<IUser>('User', UserSchema);

Payment History Model

Track payment transactions and subscription changes:

models/PaymentHistory.ts
interface IPaymentHistory extends Document {
userId: string;
provider: 'stripe' | 'lemonsqueezy';
status: 'pending' | 'succeeded' | 'failed';
amount: number;
currency: string;
planId: string;
creditsAllocated: number;
stripePaymentIntentId?: string;
lemonsqueezyOrderId?: string;
}

Usage Examples

Creating a User

app/api/users/route.ts
import dbConnect from '@/lib/mongodb';
import User from '@/models/User';
export async function POST(request: Request) {
try {
await dbConnect();
const { email, firstName, lastName } = await request.json();
const user = new User({
emailAddress: [email],
firstName,
lastName,
credits: 3
});
await user.save();
return Response.json({ success: true, user });
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
}

Querying Users

app/api/users/route.ts
export async function GET() {
try {
await dbConnect();
// Get all users
const users = await User.find();
// Get user by email
const user = await User.findOne({ emailAddress: '[email protected]' });
// Get premium users only
const premiumUsers = await User.find({ 'plan.isPremium': true });
return Response.json({ users });
} catch (error) {
return Response.json({ error: error.message }, { status: 500 });
}
}

Best Practices

Connection Management

  • • Use connection pooling for better performance
  • • Always close connections in production
  • • Handle connection errors gracefully
  • • Use environment variables for connection strings

Schema Design

  • • Use appropriate data types
  • • Add validation rules
  • • Create indexes for frequently queried fields
  • • Use references for related data

Query Optimization

  • • Use select() to limit returned fields
  • • Implement pagination for large datasets
  • • Use lean() for read-only operations
  • • Monitor query performance

Error Handling

  • • Wrap database operations in try-catch
  • • Log errors for debugging
  • • Return meaningful error messages
  • • Handle validation errors separately