janus-validate
v2.0.12
Published
A Laravel-style input validator for AWS Lambdas and microservices
Maintainers
Readme
janus-validate
A Laravel-style validation utility for Node.js projects and AWS Lambda functions.
Inspired by Janus, the Roman god of thresholds — janus-validate ensures only well-formed input crosses into your logic, and consistent responses leave it.
Installation
npm install janus-validateUsage
import { validateOrThrow } from 'janus-validate';
validateOrThrow({ email: '[email protected]' }, {
email: 'required|email'
});Error Handling
If validation fails, validateOrThrow will throw an error with the following structure:
{
message: 'Validation failed',
statusCode: 422,
errors: {
fieldName: ['Error message 1', 'Error message 2'],
anotherField: ['Another error message']
}
}This makes it ideal for returning clear HTTP 422 responses in API environments:
try {
validateOrThrow(data, rules);
} catch (err) {
return {
statusCode: err.statusCode || 500,
body: JSON.stringify({
message: err.message,
errors: err.errors
})
};
}Supported Rules
- required
- nullable
- string, integer, boolean, array
- min:X, max:X (works on string, number, array)
- in:val1,val2,val3
- required_if_not_null:field
- required_if_value:field,val
- uuid
- regex:expression
- date_format:format
- array
- object (must be used for associative arrays passed from PHP)
🧩 Array Path Support
Use dot notation and wildcards to validate deeply nested data:
| Rule | Meaning |
|----------------|-----------------------------------------------------|
| array.* | All elements in array must match the rule |
| array.*.name | Each object in array must have name validated |
| array.0.age | Validate first element’s age |
| array.name | Validate a flat object under key array |
🎯 Built-in Response Helpers
import {
successResponse,
badRequestResponse,
unprocessableResponse,
errorResponse,
forbiddenResponse
} from 'janus-validate';API
| Method | Status | Description |
|---------------------------------|--------|--------------------------------|
| successResponse(body) | 200 | Success with JSON body |
| badRequestResponse(message) | 400 | Bad request with message |
| unprocessableResponse(errors) | 422 | Validation-style errors |
| errorResponse(message) | 500 | Fallback server error |
| forbiddenResponse(resource) | 403 | “Forbidden {resource}” |
🆕 Response Templates (v1)
Standardize your output shapes with a tiny DSL: whitelist fields, coerce types, and fill missing values with typed defaults — for single objects or arrays.
import { formatResponse } from 'janus-validate';
const template = {
"productId": "string",
"name": "string",
"price": "decimal",
"stock": "int",
"active": "bool",
"createdAt": "date:iso",
// arrays of objects + per-item fields
"translations": "array<object>",
"translations[].en-EN": "string",
"translations[].en-US": "string",
"translations[].es-ES": "string",
"translations[].it-IT": "string",
};
const data = {
productId: 123, // -> "123"
name: "Ball",
price: 19.99, // -> "19.99" (kept as string to avoid float issues)
stock: "7", // -> 7
active: "true", // -> true
createdAt: "2024-01-01T10:15:00+02:00", // -> ISO UTC
translations: [{ "en-EN": "Events", "it-IT": "Eventi" }],
leak_me: "nope", // stripped (whitelisting)
};
const shaped = formatResponse(template, data);Result (highlight):
{
"productId": "123",
"name": "Ball",
"price": "19.99",
"stock": 7,
"active": true,
"createdAt": "2024-01-01T08:15:00.000Z",
"translations": [
{ "en-EN": "Events", "en-US": "", "es-ES": "", "it-IT": "Eventi" }
]
}Template DSL (v1)
- Primitives:
"string" | "int" | "float" | "bool" | "object" | "array" - Arrays with element type:
"array<string>","array<int>","array<object>" - Dates (normalized):
"date:iso" | "date:unix" | "date:ms" - Decimals:
"decimal"(kept as string to avoid precision loss) - Nullable (keep
nullvalues): append?or|null, e.g."string?","int|null" - Per-field defaults: use tuple
["descriptor", default]- Examples:
"name": ["string", "Unknown"],"tags": ["array<string>", ["a","b"]]
- Examples:
Paths & arrays
- Dot paths for nesting:
"owner.name": "string" - Arrays of objects: use
[]in the segment:"translations": "array<object>","translations[].en-EN": "string"
Hyphenated keys
- Quote them in the template (as shown with
en-EN,en-US, etc.).
Options
formatResponse(template, data, {
stripUnknown?: boolean; // default true (whitelist)
keepNull?: boolean; // default true (explicit null remains null)
roundInts?: "trunc" | "round" | "floor"; // default "trunc"
dateTZ?: "UTC" | string; // reserved for future; ISO is emitted in UTC
maxDepth?: number; // safety guard (default 10)
})Defaults applied when a field is missing
string -> "",int -> 0,float -> 0.0,bool -> false,object -> {},array -> [],decimal -> "0",date:* -> null- Override with per-field defaults (
["type", default]).
Null semantics
- If a value is explicitly
nulland the descriptor is nullable orkeepNull = true(default), it remainsnull. - Otherwise
nullis treated as missing and defaulted.
Security
- Unknown fields are dropped by default (
stripUnknown: true), preventing accidental leaks.
Use it inside helpers
import { formatResponse } from 'janus-validate';
export function forbiddenResponse(resource = "Resource") {
return formatResponse(
{ statusCode: "int", body: "object" },
{ statusCode: 403, body: { message: `Forbidden ${resource}` } }
);
}🛠 Advanced Usage
validateOrThrow(event, rules) automatically extracts:
body(parsed JSON)queryStringParameterspathParameters
You can also pass a raw object instead of a Lambda event.
Notes & Tips
- Prefer
decimal(string) or integer minor units (e.g., cents) for money to avoid float rounding. date:isooutputs UTC ISO strings.date:unixreturns seconds since epoch;date:msreturns milliseconds.- Output key order is deterministic (sorted by template paths), which is great for snapshot tests.
Janus watches your inputs — and your outputs 🛡️
