Configuration
The Mongalayer class is the central entry point on the server side. It connects to MongoDB, holds your collection definitions (schemas + access rules), and executes operations.
Check out the AWS Lambda / Express examples in Quick start for how to integrate with your HTTP server.
Creating an instance
import { MongoClient } from "mongodb";
import { Mongalayer } from "@mongalayer/server";
const mongoClient = new MongoClient("mongodb://localhost:27017");
const layer = new Mongalayer(mongoClient, {
// Each key is a MongoDB collection name
users: {
schema: userSchema,
access: userAccess
},
projects: {
schema: projectSchema,
access: projectAccess
}
});
The first argument is a standard MongoDB MongoClient instance. The second argument is an object mapping collection names to their configuration, each containing:
schema— a Zod object schema describing the document structureaccess— an array of access definitions (see Access Control)
Options
You can pass an optional third argument to configure behavior:
const layer = new Mongalayer(mongoClient, collections, {
debugging: false, // Enable debug logging (default: false)
accessDefaults: {
document: AccessPermissions.Read, // Default document permission
delete: false // Default delete permission
}
});
debugging
When true, Mongalayer logs debug information to the console and throws the original Zod/MongoDB errors instead of wrapping them. Useful during development.
You can also enable debugging via the MONGALAYER_DEBUG=1 environment variable.
accessDefaults
Default permissions applied when a document does not match any access role, or when a role doesn't explicitly define a permission:
| Property | Type | Default | Description |
|---|---|---|---|
document | AccessPermission | AccessPermissions.Read | Default document-level permission |
delete | boolean | false | Whether documents can be deleted by default |
Executing operations
Mongalayer provides three methods for executing operations:
executeJSON(stringifiedBody, accessPayload) => string
Accepts a JSON-stringified body and returns a JSON string result. Unlike execute, it supports both regular (single) and batch operations through the same entry point. It handles serialization of special types like Date and RegExp. You can directly pass the HTTP request body, coming from the Mongalayer client, to this method. The body is either a single { action, payload } object or an array of them for batch operations.
Single operation example
const singleBody = `{
"action":{"database":"myapp","collection":"projects","operation":"findOne"},
"payload":{"filter":{"_id":"project-123"}}
}`;
const single = await layer.executeJSON(singleBody, { user: { id: "user-123" } });
Batch operation example
// Batch of read operations — results are returned in order
const batchBody = `[
{
"action": { "database": "myapp", "collection": "projects", "operation": "findOne" },
"payload": { "filter": { "_id": "project-123" } }
},
{
"action": { "database": "myapp", "collection": "assets", "operation": "find" },
"payload": { "filter": { "projectID": "project-123" }, "options": { "limit": 10 } }
}
]`;
const batch = await layer.executeJSON(batchBody, { user: { id: "user-123" } });
findOne, find, aggregate). A batch containing a write operation is rejected during validation.execute(action, stringifiedPayload, accessPayload) => string
Executes a single action. Accepts a JSON-stringified payload and returns a JSON string result. Handles serialization of special types like Date and RegExp. Prefer executeJSON unless you specifically need to pass the action and payload separately, for instance when using the routed format.
const payload = `{"filter":{},"options":{"limit":10}}`;
const result = await layer.execute(
{ database: "myapp", collection: "projects", operation: "find" },
payload,
{ user: { id: "user-123" } }
);
// result is a JSON string from a serialized object
executeRaw(action, payload, accessPayload) => object
For server-side use when you don't want to use serialization. Accepts and returns native JavaScript objects.
executeRaw, you must ensure that the payload is properly formatted and does not contain unsupported types. We advice you to use executeJSON when using the client SDK.const payload = { filter: {}, options: { limit: 10 } };
const result = await layer.executeRaw(
{ database: "myapp", collection: "projects", operation: "find" },
payload,
{ user: { id: "user-123" } }
);
// result is already a JavaScript object
Action structure
Every operation requires an action object with three properties:
{
database: string, // MongoDB database name
collection: string, // Collection name (must match a key in your collections config)
operation: string // One of the supported operations
}
Supported operations
| Operation | Description | Batch support |
|---|---|---|
findOne | Find a single document | ✅ |
find | Find multiple documents | ✅ |
findOneAndUpdate | Find and update a single document, returning the result | |
aggregate | Run an aggregation pipeline | ✅ |
insertOne | Insert a single document | |
insertMany | Insert multiple documents | |
updateOne | Update a single document | |
updateMany | Update multiple documents | |
deleteOne | Delete a single document | |
deleteMany | Delete multiple documents |
Action payload validation
Mongalayer already validates the structure of the action structure when calling executeJSON, execute or executeRaw.
If necesarry the package also exports this validateAction function that you can use to validate the action structure separately:
import { validateAction } from "@mongalayer/server";
const action = { database: "myapp", collection: "projects", operation: "find" };
try {
validateAction(action);
// Action is valid, proceed with execution
} catch (error) {
// Handle invalid action error
}
Access payload
The third argument to executeJSON / execute / executeRaw is the access payload — a free-form object that becomes available in your access filters via the %% prefix.
// On the server, after verifying the user's JWT or session
const accessPayload = {
user: {
id: decodedToken.id,
roles: decodedToken.roles,
organizationId: decodedToken.org
}
};
await layer.execute(action, payload, accessPayload);
In your access definitions, you reference these values:
{
role: "member",
filter: {
organizationId: "%%user.organizationId"
}
}