Skip to content

When to use it (and when not to)

Use mongoose-guard when a route receives a JSON body that will be saved, more or less as is, into a Mongoose model. Use something else for request data that has no model behind it: query strings, route params, headers, login forms, search filters, and payloads that get transformed heavily before they reach the database. Most apps end up using mongoose-guard for create and update routes and a general-purpose validator for the rest.

CRUD APIs. “Create product”, “update profile”, “add comment”. The body is a slice of a model, so the model already knows every rule.

router.post("/products", guard(Product, { allow: ["name", "price", "stock"] }), createProduct);
router.patch("/products/:id", guard(Product, { allow: ["price", "stock"], partial: true }), updateProduct);

Admin panels and internal tools. Lots of forms, each one editing a few fields of a model. Writing a Zod schema per form is busywork.

Models with sensitive fields. Anything with role, isAdmin, balance, credits, ownerId, verified or plan. The allow list makes “who can set what” explicit on every route.

Teams tired of keeping two schemas in sync. If you’ve ever shipped a bug because the Zod schema said max(100) and the Mongoose schema said maxLength: 80, this is the problem mongoose-guard removes.

Existing Express apps that use req.body directly. You can add a guard to one route at a time without restructuring anything.

Requests with no model behind them. A login body ({ email, password }), a password reset, a search request or a payment webhook doesn’t map onto a Mongoose model. There is nothing for mongoose-guard to read.

Query strings, route params and headers. mongoose-guard validates bodies. ?page=2&sort=price, /users/:id and Authorization need another tool.

Bodies that get transformed before saving. If the client sends { "fullName": "Amrit Rai" } and you store { first: "Amrit", last: "Rai" }, the request and the model have different shapes. Validate the request with a schema written for the request.

Form posts. HTML forms send every value as a string. mongoose-guard doesn’t convert types, so age: "25" fails a Number field. Convert the values yourself first, or use a validator with built-in coercion, such as z.coerce.number() in Zod.

Edge runtimes. Cloudflare Workers, Vercel Edge and similar runtimes can’t run Mongoose, so they can’t run mongoose-guard either.

Projects that don’t use Mongoose. Prisma, Drizzle, TypeORM and the native MongoDB driver aren’t supported. mongoose-guard reads Mongoose schemas specifically.

SituationUse mongoose-guard?
POST /users saves the body as a new userYes
PATCH /users/:id updates a few user fieldsYes, with partial: true
POST /login checks an email and passwordNo, there’s no Login model
GET /products?minPrice=10No, that’s a query string
POST /orders builds an order from a cart ID and a coupon codeNo, the body doesn’t look like an Order
An admin form edits ten fields of a modelYes
A webhook from StripeNo, verify the signature and validate with its own schema
A Next.js route handler that creates a documentYes, with mongoose-guard/web
A Next.js server action that receives FormDataOnly after you convert strings to the right types
A Cloudflare WorkerNo, Mongoose can’t run there

Using two tools is normal, and they don’t conflict. A typical Express app looks like this:

import { z } from "zod";
import { guard } from "mongoose-guard/express";
// No model behind it: Zod
const LoginBody = z.object({ email: z.string().email(), password: z.string() });
router.post("/login", (req, res) => {
const result = LoginBody.safeParse(req.body);
if (!result.success) return res.status(400).json({ errors: result.error.issues });
// ...
});
// Saves into a model: mongoose-guard
router.post("/users", guard(User, { allow: ["name", "email", "password"] }), createUser);

Ask yourself one question about the route: “Is the request body a subset of a document I’m about to save?” If yes, mongoose-guard fits. If you have to explain how the body turns into a document, use a schema written for the request.