Mongoose models over direct collections, findOne() not findById(), licencee filtering, aggregation patterns.
Use for all backend database operations in app/api/.
Never use db.collection() directly:
// ✅ CORRECT - Use Mongoose model
import { Member } from '@/app/api/lib/models/members';
const members = await Member.find(query).lean();
const count = await Member.countDocuments(query);
// ❌ INCORRECT - Direct collection access
const members = await db.collection('members').find(query).toArray();
const count = await db.collection('members').countDocuments(query);
Benefits:
Use findOne() with string IDs:
// ✅ CORRECT - findOne for string IDs
const session = await MachineSession.findOne({ _id: sessionId });
const user = await User.findOne({ _id: userId });
// ❌ INCORRECT - findById (expects ObjectId)
const session = await MachineSession.findById(sessionId);
const user = await User.findById(userId);
Updates with findOneAndUpdate():
// ✅ CORRECT
const updated = await Model.findOneAndUpdate(
{ _id: id },
{ $set: { field: newValue } },
{ new: true }
);
// ❌ INCORRECT
const updated = await Model.findByIdAndUpdate(id, { field: newValue });
ALWAYS apply location filters to location/machine queries:
import { getUserLocationFilter } from '@/app/api/lib/helpers/licenceeFilter';
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
// Support both spellings
const licencee = searchParams.get('licencee') || searchParams.get('licencee');
// Get allowed locations for user
const allowedLocationIds = await getUserLocationFilter(licencee || undefined);
// Handle no access
if (allowedLocationIds !== 'all' && allowedLocationIds.length === 0) {
return NextResponse.json({ success: true, data: [] });
}
// Build query
const query: Record<string, unknown> = {};
if (allowedLocationIds !== 'all') {
query._id = { $in: allowedLocationIds }; // For locations
// OR
query.gamingLocation = { $in: allowedLocationIds }; // For machines
}
// Execute
const data = await GamingLocations.find(query);
return NextResponse.json({ success: true, data });
}
const pipeline: PipelineStage[] = [
// ============================================================================
// Stage 1: Match documents
// ============================================================================
{
$match: {
readAt: { $gte: startDate, $lte: endDate },
},
},
// ============================================================================
// Stage 2: Filter by location if user has restrictions
// ============================================================================
...(allowedLocationIds !== 'all'
? [{ $match: { location: { $in: allowedLocationIds } } }]
: []),
// ============================================================================
// Stage 3: Group results
// ============================================================================
{
$group: {
_id: '$location',
total: { $sum: '$movement.drop' },
},
},
];
const results = await Model.aggregate(pipeline);
const pipeline: PipelineStage[] = [
{
$match: {
gamingLocation: { $in: allowedLocationIds },
},
},
{
$lookup: {
from: 'gaminglocations',
localField: 'gamingLocation',
foreignField: '_id',
as: 'locationDetails',
},
},
{
$unwind: '$locationDetails',
},
{
$match: {
'locationDetails.rel.licencee': licenceeId,
},
},
];
Always filter out soft-deleted records:
// ✅ CORRECT - Exclude soft-deleted
const locations = await GamingLocations.find({
$or: [
{ deletedAt: null },
{ deletedAt: { $lt: new Date('2025-01-01') } },
],
});
const machines = await Machine.find({
gamingLocation: { $in: locationIds },
$or: [
{ deletedAt: null },
{ deletedAt: { $lt: new Date('2025-01-01') } },
],
});
try {
// Database operations
const data = await Model.findOne({ _id: id });
if (!data) {
return NextResponse.json(
{ success: false, message: 'Not found' },
{ status: 404 }
);
}
return NextResponse.json({ success: true, data });
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
console.error('[Route Name] Error:', errorMessage);
return NextResponse.json(
{ success: false, error: errorMessage, message: 'Database operation failed' },
{ status: 500 }
);
}
const item = await Model.findOne({ _id: id });
if (!item) return notFoundResponse();
const items = await Model.find({ _id: { $in: ids } });
const count = await Model.countDocuments(query);
const updated = await Model.findOneAndUpdate(
{ _id: id },
{ $set: updateObject },
{ new: true }
);
const result = await Model.updateMany(
{ query },
{ $set: updateObject }
);
const deleted = await Model.findOneAndUpdate(
{ _id: id },
{ $set: { deletedAt: new Date() } },
{ new: true }
);
// Archive location
await GamingLocations.findOneAndUpdate(
{ _id: locationId },
{ $set: { deletedAt: new Date() } }
);
// Archive all machines at that location
await Machine.updateMany(
{ gamingLocation: locationId },
{ $set: { deletedAt: new Date() } }
);
Use .lean() when you don't need Mongoose document methods:
// ✅ CORRECT - Use lean for read-only data
const machines = await Machine.find(query).lean();
const report = await Machine.aggregate(pipeline).exec();
// Use lean() only for queries that return documents
const machines = await Machine.find(query).lean(); // ✅ Good
const count = await Machine.countDocuments(query); // ✅ No lean needed
db.collection()findOne() not findById()findOneAndUpdate() not findByIdAndUpdate()licencee spellings supported.lean() used for read-only queriesdb.collection() access