Quick start
This page builds a small Express API with one route protected by mongoose-guard. It takes about five minutes. You don’t need MongoDB running, because validation happens in memory. You only need Node.js 20 or newer.
-
Create a project and install the packages
Terminal window mkdir guard-demo && cd guard-demonpm init -ynpm pkg set type=modulenpm install express mongoose mongoose-guardnpm pkg set type=modulelets you useimportsyntax in plain.jsfiles. -
Write a model
Create
user.js. This is a normal Mongoose model. Nothing in it is specific to mongoose-guard.user.js import mongoose from "mongoose";const userSchema = new mongoose.Schema({name: { type: String, required: true, minLength: 2 },email: { type: String, required: true, lowercase: true },password: { type: String, required: true, minLength: 8 },age: { type: Number, min: 13 },role: { type: String, enum: ["user", "admin"], default: "user" },credits: { type: Number, default: 0 },});export const User = mongoose.model("User", userSchema); -
Write the server
Create
server.js:server.js import express from "express";import { guard } from "mongoose-guard/express";import { User } from "./user.js";const app = express();app.use(express.json());app.post("/signup",guard(User, { allow: ["name", "email", "password", "age"] }),(req, res) => {res.status(201).json({ received: req.validated });},);app.listen(3000, () => console.log("Listening on http://localhost:3000"));Two things to notice:
express.json()must come before the guard. It turns the raw request intoreq.body. Without it, every request fails withinvalid_body.- The handler reads
req.validated, notreq.body.req.validatedholds only the fields that passed.
-
Start it
Terminal window node server.js -
Send a valid request
In a second terminal:
Terminal window curl -s -X POST localhost:3000/signup \-H 'content-type: application/json' \-d '{"name":"Amrit","email":"amrit@example.com","password":"correct-horse","age":25}'{"received":{"name":"Amrit","email":"amrit@example.com","password":"correct-horse","age":25}} -
Try to make yourself an admin
Terminal window curl -s -X POST localhost:3000/signup \-H 'content-type: application/json' \-d '{"name":"Amrit","email":"amrit@example.com","password":"correct-horse","role":"admin","credits":99999}'{"errors":[{"code":"forbidden_field","path":"role","message":"Field is not allowed"},{"code":"forbidden_field","path":"credits","message":"Field is not allowed"}]}The status is
400 Bad Requestand the handler never ran.roleandcreditsare real fields in the schema, but this route didn’t allow them. -
Send a number as a string
Terminal window curl -s -X POST localhost:3000/signup \-H 'content-type: application/json' \-d '{"name":"Amrit","email":"amrit@example.com","password":"correct-horse","age":"25"}'{"errors":[{"code":"invalid_type","path":"age","message":"Expected number, received string"}]}Mongoose on its own would have quietly turned
"25"into25. mongoose-guard treats it as a client bug and says so. -
Break the schema’s own rules
Terminal window curl -s -X POST localhost:3000/signup \-H 'content-type: application/json' \-d '{"name":"A","password":"123"}'{"errors":[{"code":"invalid_value","path":"email","message":"Path `email` is required.","rule":"required"},{"code":"invalid_value","path":"name","message":"Path `name` (`A`, length 1) is shorter than the minimum allowed length (2).","rule":"minlength"},{"code":"invalid_value","path":"password","message":"Path `password` (`123`, length 3) is shorter than the minimum allowed length (8).","rule":"minlength"}]}These rules came straight from
user.js. You didn’t write them twice.
Saving to MongoDB
Section titled “Saving to MongoDB”Once you connect to a database, save req.validated exactly as you would have saved req.body:
await mongoose.connect(process.env.MONGODB_URI);
app.post( "/signup", guard(User, { allow: ["name", "email", "password", "age"] }), async (req, res) => { const user = await User.create(req.validated); res.status(201).json({ id: user.id }); },);What you learned
Section titled “What you learned”guard(Model, { allow })goes betweenexpress.json()and your handler.allowlists the fields this route accepts. Everything else in the schema is off limits for this route.- Bad requests get a
400with anerrorsarray. Each error has acode, apathand amessage. - Good requests reach your handler with the checked data on
req.validated.
Next steps
Section titled “Next steps”- When to use it: decide whether mongoose-guard fits your project.
- Partial updates: set up a
PATCHroute. - Nested data: allow
address.citybut notaddress.zip. - Custom error responses: change the 400 response shape.