@mongalayer/server

Configuration

How to create and configure a Mongalayer server instance.

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 structure
  • access — 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:

PropertyTypeDefaultDescription
documentAccessPermissionAccessPermissions.ReadDefault document-level permission
deletebooleanfalseWhether documents can be deleted by default

Executing operations

Mongalayer provides three methods for executing operations:

executeJSON(stringifiedBody, accessPayload) => string

This is the preferred method for HTTP integrations. It validates the whole body payload by default and supports both regular (single) and batch operations through the same entry point.

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" } });
Batch bodies may only contain read operations (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.

The client SDK uses the same (de)serialization methods.
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.

When using 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

OperationDescriptionBatch support
findOneFind a single document
findFind multiple documents
findOneAndUpdateFind and update a single document, returning the result
aggregateRun an aggregation pipeline
insertOneInsert a single document
insertManyInsert multiple documents
updateOneUpdate a single document
updateManyUpdate multiple documents
deleteOneDelete a single document
deleteManyDelete 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"
    }
}
See Access Control for the full access system documentation.
Copyright © 2026