Serialization
MongoDB documents contain BSON types (ObjectId, Date, Long, etc.) that can't be passed directly to Client Components in Next.js or serialized to JSON. Monch provides built-in serialization to handle this.
Automatic Serialization (Express, Fastify, etc.)
For Express and other frameworks, Monch documents have a toJSON() method that's called automatically by JSON.stringify() and res.json():
// Express - works automatically!
app.get('/api/users', async (req, res) => {
const users = await Users.find({}).toArray();
res.json(users); // toJSON() called automatically
});
app.get('/api/users/:id', async (req, res) => {
const user = await Users.findById(req.params.id);
res.json(user); // toJSON() called automatically
});No manual serialization needed - BSON types are converted to JSON-safe values automatically.
Next.js (Manual Serialization Required)
Next.js blocks objects with toJSON at the Server/Client boundary. Use .serialize() explicitly:
// Server Component
const user = await Users.findOne({ email: 'alice@example.com' });
// This fails! Next.js blocks toJSON
return <UserProfile user={user} />; // ❌ Error
// Use .serialize() for Next.js
const plain = user.serialize();
return <UserProfile user={plain} />; // ✅ WorksSingle Document
const user = await Users.findOne({ email: 'alice@example.com' });
// Before serialization
console.log(user._id); // ObjectId('507f1f77bcf86cd799439011')
console.log(user.createdAt); // Date object
// After serialization
const plain = user.serialize();
console.log(plain._id); // '507f1f77bcf86cd799439011' (string)
console.log(plain.createdAt); // '2024-01-15T10:30:00.000Z' (ISO string)Array of Documents
Method 1: Serialize on Cursor (Recommended)
const users = await Users.find({ role: 'admin' }).serialize();
// Returns Serialized<User>[] - already serializedMethod 2: Serialize After toArray
const users = await Users.find({ role: 'admin' }).toArray();
const plain = users.serialize();
// MonchArray has a .serialize() methodBoth methods work. The first is more concise.
Type-Safe Serialization
import { type Serialized, type ModelOf, type SerializedOf } from '@codician-team/monch';
// Document type
type User = ModelOf<typeof Users>;
// { _id: ObjectId, name: string, email: string, createdAt: Date, updatedAt: Date }
// Serialized type (automatic conversion)
type SerializedUser = SerializedOf<typeof Users>;
// { _id: string, name: string, email: string, createdAt: string, updatedAt: string }
// Or manually
type ManualSerialized = Serialized<User>;
// Use in components
interface UserCardProps {
user: SerializedUser; // Type-safe serialized user
}Conversion Table
| BSON Type | Serialized To | Example |
|---|---|---|
ObjectId | string (hex) | '507f1f77bcf86cd799439011' |
Date | string (ISO 8601) | '2024-01-15T10:30:00.000Z' |
Long | number or bigint* | 9007199254740991 |
Decimal128 | string | '99999999999999.99' |
Binary | string (base64) | 'aGVsbG8=' |
Timestamp | { t: number, i: number } | { t: 1234567890, i: 1 } |
*Long serializes to number when within JavaScript's safe integer range (±9,007,199,254,740,991). Values outside this range serialize to bigint to preserve precision.
Standalone Serialization
You can serialize data without fetching from a collection:
import { serialize, serializeArray } from '@codician-team/monch';
// Single document
const plain = serialize(document);
// Array of documents
const plainArray = serializeArray(documents);MonchArray
When you call .toArray(), Monch returns a MonchArray instead of a regular array. This is an array subclass with a .serialize() method:
const users = await Users.find({ role: 'admin' }).toArray();
// It's still an array
users.length; // Works
users.map(...); // Works
users.filter(...); // Works
// But with extra functionality
users.serialize(); // Serialize all documentsNext.js Integration
Server Component to Client Component
// app/users/page.tsx (Server Component)
import { Users } from '@/lib/models';
import { UserList } from './UserList';
export default async function UsersPage() {
const users = await Users.find({ status: 'active' })
.sort({ createdAt: -1 })
.limit(20)
.serialize(); // Serialize here
return <UserList users={users} />;
}// app/users/UserList.tsx (Client Component)
'use client';
import type { SerializedUser } from '@/lib/models';
interface Props {
users: SerializedUser[];
}
export function UserList({ users }: Props) {
return (
<ul>
{users.map((user) => (
<li key={user._id}>{user.name}</li>
))}
</ul>
);
}Server Actions
// app/actions/user.actions.ts
'use server';
import { Users } from '@/lib/models';
export async function getUser(id: string) {
const user = await Users.findById(id);
return user?.serialize() ?? null;
}
export async function searchUsers(query: string) {
const users = await Users.find({
$text: { $search: query },
}).serialize();
return users;
}Pagination with Serialization
export async function getUsers(page: number) {
const result = await Users.find({ status: 'active' })
.sort({ createdAt: -1 })
.paginate({ page, limit: 20 });
return {
...result,
data: result.data.map(user => user.serialize()),
};
}When to Serialize
| Scenario | Method | Notes |
|---|---|---|
Express res.json() | Automatic | toJSON() called by JSON.stringify() |
| Fastify response | Automatic | toJSON() called by JSON.stringify() |
| Next.js Client Component | .serialize() | Next.js blocks toJSON |
| Next.js Server Action | .serialize() | Next.js blocks toJSON |
JSON.stringify() | Automatic | toJSON() called automatically |
| Server-only processing | None | Keep BSON types |
| Storing back to MongoDB | None | Keep BSON types |
Common Patterns
Optional Chaining
const user = await Users.findOne({ email });
return user?.serialize() ?? null;Conditional Serialization
async function getUser(id: string, serialize = true) {
const user = await Users.findById(id);
if (!user) return null;
return serialize ? user.serialize() : user;
}Map with Serialization
const users = await Users.find({ role: 'admin' }).toArray();
const enriched = users.map(user => ({
...user.serialize(),
displayName: `${user.name} (${user.role})`,
}));