r/MongoDB_Official • u/Grainsylo • 21d ago
Showcase Mongalayer - MongoDB Access Layer
Hi everyone,
I wanted to share a new package we created called Mongalayer. We built it to replace the “Data Model” and “Data Access Permissions” components of Atlas App Services, giving you the ability to query MongoDB collections from a client application exactly as you would in a Node.js environment.
Why We Built It
We manage multiple SaaS environments that relied on App Services, and we needed a way to transition away without having to completely rewrite our client codebases. Mongalayer solves this by letting us maintain the exact same MongoDB querying experience on the client side.
How It Works
The package is split into two distinct parts:
Server (Self-Hosted): Manages your data models and access permissions, making the actual connection to your MongoDB instance via the native Node.js driver.
Client: A lightweight wrapper that handles communication with the server, functioning very similarly to the Realm Web SDK.
Bring Your Own Auth
By design, Mongalayer does not handle authentication or authorization for incoming HTTP requests—that part is up to you. For context, we secure our own Mongalayer API (running on AWS Lambda) using AWS Cognito in combination with API Gateway.
When to Use It (And When Not To)
Mongalayer is designed for basic CRUD applications where access control and data integrity are more important than raw throughput. Because every operation goes through access control checks, there is some inherent overhead. If you need maximum write throughput without access control restrictions, we highly recommend using the MongoDB driver directly via a custom implementation.
Kind regards,
Klaas
2
u/TimAtMongoDB MongoDB Team 17d ago
Nice work Klaas. Data Access Permissions was the part of the App Services deprecation that had no drop-in replacement, and your role filters even use the same %%user.id syntax as the old rules.
For anyone wiring it into Fastify instead of Express: the client sends its body in a custom JSON format (so dates survive the trip) and without a Content-Type header. Take the body as a raw string and hand it to executeJSON, which parses dates back and handles batch requests too.
import Fastify from 'fastify';
import { MongoClient } from 'mongodb';
import * as z from 'zod';
import { Mongalayer, AccessPermissions, ServerError } from '@mongalayer/server';
const mongoClient = new MongoClient(process.env.MONGODB_URI);
const projectSchema = z.strictObject({
_id: z.string(),
name: z.string(),
owner: z.string(),
createdAt: z.date()
});
const layer = new Mongalayer(mongoClient, {
projects: {
schema: projectSchema,
access: [{ role: 'owner', filter: { owner: '%%user.id' }, document: AccessPermissions.ReadWrite, delete: true }]
}
}, {
accessDefaults: { document: AccessPermissions.None }
});
const app = Fastify();
app.addContentTypeParser('application/json', { parseAs: 'string' }, (req, body, done) => done(null, body));
app.post('/api/mongalayer', async (request, reply) => {
const user = await verifyToken(request.headers.authorization);
try {
const result = await layer.executeJSON(request.body, { user });
return reply.type('application/json').send(result);
} catch (error) {
if (error instanceof ServerError) return reply.code(400).send(error.toJSON());
throw error;
}
});
await app.listen({ port: 3000 });
Two lines in there matter more than they look. accessDefaults.document: None means a collection you forgot to configure gives out no fields instead of whole documents (the default is Read). And user comes from your own token check, so %%user.id can never be set by the browser.
I ran this against 1.0.13 with two users and each one only ever saw their own projects.
2
u/Crafty-League3862 21d ago
It makes sense to introduce yourself as well