nox-mongo-driver
v1.4.0
Published
A fast runtime schema-driven sanitizer & validator for dynamic schemas — alternative to Mongoose validation for multi-tenant apps.
Downloads
257
Readme
nox-mongo-driver
A fast, async-first, schema-driven payload sanitizer, validator & composable rule engine for Node.js and MongoDB applications. Built as a lightweight alternative to Mongoose validation — ideal for multi-tenant systems where schemas are defined dynamically at runtime.
Interactive Docs & Playground: nox-sanitizer.nowonlinetech.in
Installation
npm install nox-mongo-driverQuick Start
import { sanitize } from "nox-mongo-driver";
const schema = {
name: { type: "String", required: true, trim: true },
age: { type: "Number", validation: { _between: [18, 65] } },
email: {
type: "String",
validation: {
_and: [
{ _regex: "^[^@]+@[^@]+\\.[a-zA-Z]{2,}$" },
{ _not: { _contains: "spam" } },
],
},
},
};
const { value, errors } = await sanitize(
{ name: " Sahil ", age: "25", email: "[email protected]" },
schema,
{ mode: "strict" }
);
// value → { name: "Sahil", age: 25, email: "[email protected]" }
// errors → []Try this example live in the Playground
Features
Schema Sanitizer
- Three processing modes — strict, transform, validate (docs)
- Type coercion — strings to numbers, booleans, dates, ObjectIds
- Rule validation — required, min/max, enum, regex, minLength/maxLength
- Nested objects & arrays — inline and explicit styles
- Bare Object / Array types — type-only validation without nested schema
- Recursive schemas — self-referencing via lazy functions
- Custom validators — sync and async with context injection
- Setter functions — transform values before coercion
- Immutable fields — prevent updates on specific fields
- Auto timestamps — createdAt / updatedAt with customizable field names
- Partial mode — skip required checks for PATCH / update operations
- Bulk sanitization — process arrays of payloads with concurrency control
- Cleanup options — strip nulls, empty objects, empty arrays, undefined
- Unknown field handling — strip, keep, or error
- Prototype pollution protection — blocks
__proto__,constructor,prototype - i18n support — custom error messages per language
- Stop on first error — bail early for performance
Composable Rule Engine
- 20 comparison operators —
_eq,_neq,_lt,_lte,_gt,_gte,_in,_nin,_contains,_contains_i,_starts_with,_starts_with_i,_ends_with,_ends_with_i,_is_null,_regex,_between,_length,_empty,_not_empty(operator reference) - 3 logical operators —
_and,_or,_not(unlimited nesting) - Cross-field references —
_refto compare against sibling, parent, or root fields - Conditional validation —
whento apply rules based on another field's value - Per-operator error codes — each operator produces its own specific error code and message (error codes)
- i18n per operator — translate each operator message independently
Documentation
Full interactive documentation with live playground, operator reference, schema options, and error codes:
nox-sanitizer.nowonlinetech.in
- Playground — edit schemas, payloads, and options live with 34 preset examples
- Operator Reference — all 23 operators with descriptions, examples, and error codes
- Features — detailed docs for every feature with code examples
- Schema Options — searchable table of all 21 schema field options
- Error Codes — all 37 error codes with triggers and default messages
Modes
| Mode | Type Validation | Rule Validation | Transformation |
| ----------- | --------------- | --------------- | -------------- |
| strict | Yes | Yes | Yes |
| transform | Yes | No | Yes |
| validate | Yes | Yes | No |
await sanitize(payload, schema, { mode: "transform" });Schema Field Options
Full searchable reference: Schema Options
{
type: "String" | "Number" | "Boolean" | "Date" | "ObjectId" | "Object" | "Array" | "Mixed",
required: boolean,
default: any | (() => any),
enum: any[],
match: RegExp,
min: number,
max: number,
minLength: number,
maxLength: number,
trim: boolean,
lowercase: boolean,
uppercase: boolean,
immutable: boolean,
set: (value, context) => any,
validate: (value, context) => boolean | string,
asyncValidate: async (value, context) => boolean | string,
validation: { /* composable rule engine */ },
when: { field, is, then, otherwise },
}Composable Rule Engine (validation key)
Interactive examples: Playground > Operators
The validation key on any schema field accepts a composable rule tree. Rules can be nested with _and, _or, and _not.
Comparison Operators
Full reference with error codes: Operator Reference
| Operator | Description | Example |
|---|---|---|
| _eq | Equals | { _eq: "active" } |
| _neq | Not equals | { _neq: "deleted" } |
| _lt | Less than | { _lt: 100 } |
| _lte | Less than or equal | { _lte: 65 } |
| _gt | Greater than | { _gt: 0 } |
| _gte | Greater than or equal | { _gte: 18 } |
| _in | Value in array | { _in: ["admin", "editor"] } |
| _nin | Value not in array | { _nin: ["banned"] } |
| _contains | String contains | { _contains: "hello" } |
| _contains_i | Contains (case insensitive) | { _contains_i: "Hello" } |
| _starts_with | Starts with | { _starts_with: "admin" } |
| _starts_with_i | Starts with (case insensitive) | { _starts_with_i: "Admin" } |
| _ends_with | Ends with | { _ends_with: ".pdf" } |
| _ends_with_i | Ends with (case insensitive) | { _ends_with_i: ".PDF" } |
| _is_null | Is null/undefined | { _is_null: true } |
| _regex | Regex match | { _regex: "^[A-Z]" } or { _regex: { pattern: "^[a-z]", flags: "i" } } |
| _between | Value in range (inclusive) | { _between: [18, 65] } |
| _length | Check string/array length | { _length: { _gte: 3, _lte: 50 } } |
| _empty | Is empty (string/array/object/null) | { _empty: true } |
| _not_empty | Is not empty | { _not_empty: true } |
Logical Operators
// _and — all conditions must pass
validation: {
_and: [
{ _gte: 18 },
{ _lte: 65 }
]
}
// _or — at least one must pass
validation: {
_or: [
{ _eq: "active" },
{ _eq: "pending" }
]
}
// _not — negation
validation: {
_not: { _contains: "admin" }
}
// deep nesting
validation: {
_and: [
{ _starts_with: "user_" },
{ _not: { _in: ["user_banned", "user_suspended"] } },
{ _or: [
{ _ends_with: "_admin" },
{ _ends_with: "_editor" }
]}
]
}Cross-Field References (_ref)
Try it live: Playground > Cross-Field
Compare a field's value against another field anywhere in the payload.
const schema = {
password: { type: "String", required: true },
confirmPassword: {
type: "String",
validation: { _eq: { _ref: "password" } },
},
startDate: { type: "Date" },
endDate: {
type: "Date",
validation: { _gt: { _ref: "startDate" } },
},
};Path Resolution
| Syntax | Resolves To |
|---|---|
| { _ref: "fieldName" } | Sibling field in same parent object |
| { _ref: "$root.path.to.field" } | Absolute path from root payload |
| { _ref: "$parent.field" } | One level up |
| { _ref: "$parent.$parent.field" } | Two levels up |
Works at Any Nesting Depth
const schema = {
maxQuantity: { type: "Number" },
orders: [{
items: [{
quantity: {
type: "Number",
validation: { _lte: { _ref: "$root.maxQuantity" } },
},
}],
}],
};Conditional Validation (when)
Try it live: Playground > Conditional
Apply different validation rules based on another field's value.
const schema = {
type: { type: "String", enum: ["individual", "company"] },
companyName: {
type: "String",
when: {
field: "type",
is: { _eq: "company" },
then: { required: true },
otherwise: { required: false },
},
},
};when with Validation Rules
const schema = {
country: { type: "String" },
taxId: {
type: "String",
when: {
field: "country",
is: { _eq: "NL" },
then: { required: true, validation: { _starts_with: "NL" } },
},
},
};when with $root Reference (Inside Arrays)
const schema = {
currency: { type: "String" },
items: [{
price: {
type: "Number",
when: {
field: "$root.currency",
is: { _eq: "EUR" },
then: { validation: { _gte: 0.01 } },
},
},
}],
};Type Coercion
In strict or transform mode, values are automatically coerced to the declared type:
const schema = {
age: { type: "Number" },
isActive: { type: "Boolean" },
createdAt: { type: "Date" },
};
// Input: { age: "25", isActive: "true", createdAt: "2024-01-01" }
// Output: { age: 25, isActive: true, createdAt: Date("2024-01-01") }Nested Objects
// Inline nested
{ profile: { age: { type: "Number" }, email: { type: "String" } } }
// Explicit with type
{ profile: { type: "Object", schema: { age: { type: "Number" } } } }
// Bare (type-only, pass-through)
{ metadata: { type: "Object" } }Arrays
// Inline array of objects
items: [{ name: { type: "String", required: true } }]
// Explicit with bounds
items: { type: "Array", minItems: 1, maxItems: 50, schema: { name: { type: "String" } } }
// Bare (type-only)
{ tags: { type: "Array" } }Recursive Schemas (Lazy)
const MenuItemSchema = () => ({
id: { type: "String", required: true },
children: [MenuItemSchema],
});
const schema = { modules: [MenuItemSchema] };Setter (set)
{
slug: {
type: "String",
set: (value) => value.toLowerCase().replace(/\s+/g, "-"),
}
}
// Input: "My Blog Post" → Output: "my-blog-post"Custom Validation
// Sync
username: {
type: "String",
validate: (value) => value.length >= 3 || "Too short"
}
// Async
email: {
type: "String",
asyncValidate: async (value, context) => {
const exists = await context.db.findUser(value);
return !exists || "Email already exists";
}
}Immutable Fields
{ email: { type: "String", immutable: true } }
// On create (partial: false) — accepted
// On update (partial: true) — rejected with IMMUTABLE_FIELDAuto Timestamps
await sanitize(payload, schema, { timestamps: true });
// Create → { createdAt: Date, updatedAt: Date }
// Update (partial: true) → { updatedAt: Date }Partial Mode (For Updates)
await sanitize(payload, schema, { partial: true });
// Skips required + default for missing fieldsBulk Sanitization
import { sanitizeBulk } from "nox-mongo-driver";
const result = await sanitizeBulk(payloads, schema, { concurrency: 5 });Unknown Field Handling
unknownFields: "strip" // default — removes unknown fields
unknownFields: "keep" // preserves unknown fields
unknownFields: "error" // adds error for each unknown fieldCleanup Options
await sanitize(payload, schema, {
removeNull: true,
removeUndefined: true,
removeEmptyObjects: true,
removeEmptyArrays: true,
});Stop On First Error
await sanitize(payload, schema, { stopOnFirstError: true });Error Format
Full error code reference: Error Codes
{
path: "profile.age",
code: "VALIDATION_GTE",
message: "Must be greater than or equal to 18",
meta: { operator: "_gte", expected: 18, received: 15 }
}Error Codes
Schema rules: FIELD_REQUIRED · INVALID_TYPE · ENUM_MISMATCH · REGEX_MISMATCH · MIN_VIOLATION · MAX_VIOLATION · MIN_LENGTH_VIOLATION · MAX_LENGTH_VIOLATION · MIN_ITEMS_VIOLATION · MAX_ITEMS_VIOLATION · EXPECTED_ARRAY · EXPECTED_OBJECT · UNKNOWN_FIELD · IMMUTABLE_FIELD · CUSTOM_VALIDATION · CUSTOM_ASYNC_VALIDATION
Validation operators: VALIDATION_EQ · VALIDATION_NEQ · VALIDATION_LT · VALIDATION_LTE · VALIDATION_GT · VALIDATION_GTE · VALIDATION_IN · VALIDATION_NIN · VALIDATION_CONTAINS · VALIDATION_CONTAINS_I · VALIDATION_STARTS_WITH · VALIDATION_STARTS_WITH_I · VALIDATION_ENDS_WITH · VALIDATION_ENDS_WITH_I · VALIDATION_IS_NULL · VALIDATION_REGEX · VALIDATION_BETWEEN · VALIDATION_LENGTH · VALIDATION_EMPTY · VALIDATION_NOT_EMPTY · VALIDATION_NOT
i18n / Custom Messages
await sanitize(payload, schema, {
language: "nl",
messages: {
nl: {
FIELD_REQUIRED: "Veld is verplicht",
VALIDATION_CONTAINS: (expected) => `Moet "${expected}" bevatten`,
VALIDATION_GTE: (expected) => `Moet minimaal ${expected} zijn`,
},
},
});Conflict Resolution
When a validation key is present, conflicting schema rules are automatically skipped:
| Skipped Schema Rule | Use Instead |
|---|---|
| enum | _in / _nin |
| match (regex) | _regex, _contains, _starts_with, _ends_with |
| min / max | _gt, _gte, _lt, _lte, _between |
Non-conflicting rules always run: required, minLength, maxLength, validate, asyncValidate.
Recommended Usage Patterns
// Create — full validation + coercion
await sanitize(payload, schema, { mode: "strict" });
// Update — skip required, reject immutable
await sanitize(payload, schema, { mode: "strict", partial: true });
// Data transformation layer — coerce types only
await sanitize(payload, schema, { mode: "transform" });
// Validation gate — check without mutating
await sanitize(payload, schema, { mode: "validate" });Security
Automatically strips prototype pollution vectors (__proto__, constructor, prototype) from both schema keys and input data when unknownFields is set to keep.
Links
License
MIT — NOX Team
