Validating Documents with JSON Schema
🔧 Configuration
RESTHeart provides robust document validation capabilities through JSON Schema, allowing you to enforce structure and data quality in your MongoDB collections.
Introduction
Data validation ensures that documents conform to a predefined structure before they’re stored in the database. RESTHeart supports two complementary approaches to validation:
-
MongoDB’s built-in schema validation: Available in MongoDB 3.6+ using JSON Schema
-
RESTHeart’s jsonSchema Interceptor: A more flexible approach with additional features
The jsonSchema Interceptor in RESTHeart offers several advantages over MongoDB’s native validation:
-
Schemas are stored in a dedicated schema store (
/_schemas
) and are validated themselves -
Schemas can be reused across multiple collections
-
Support for complex schemas with sub-schemas using the
$ref
keyword -
Integration with online schemas
-
Performance optimization through schema caching
Understanding JSON Schema
JSON Schema is a vocabulary that allows you to annotate and validate JSON documents. It provides a contract for what JSON data is required for a given application and how it can be modified.
JSON Schema specifies a JSON-based format to define the structure of JSON data for validation, documentation, and interaction control.
JSON Schema lets you define:
-
Required and optional fields
-
Field types (string, number, object, etc.)
-
Value constraints (minimum, maximum, pattern, etc.)
-
Nested object structures
-
Array validations
For comprehensive information about JSON Schema, visit json-schema.org or the excellent guide at Understanding JSON Schema.
Setting Up Schema Validation
Step 1: Create the Schema Store
First, create a schema store to hold your JSON Schema definitions:
cURL
curl -X PUT "[INSTANCE-URL]/_schemas" \
-H "Authorization: Basic [BASIC-AUTH]"
HTTPie
http PUT "[INSTANCE-URL]/_schemas" \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
fetch('[INSTANCE-URL]/_schemas', {
method: 'PUT',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => {
if (response.ok) {
console.log('Schema store created successfully');
} else {
console.error('Failed to create schema store:', response.status);
}
})
.catch(error => console.error('Error:', error));
Step 2: Define a Schema
Create a schema document that defines the structure for your data:
cURL
curl -X PUT "[INSTANCE-URL]/_schemas/address?wm=upsert" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"$schema": "https://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"address": { "type": "string" },
"city": { "type": "string" },
"postal-code": { "type": "string" },
"country": { "type": "string"}
},
"required": ["address", "city", "country"]
}'
HTTPie
echo '{
"$schema": "https://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"address": { "type": "string" },
"city": { "type": "string" },
"postal-code": { "type": "string" },
"country": { "type": "string"}
},
"required": ["address", "city", "country"]
}' | http PUT "[INSTANCE-URL]/_schemas/address?wm=upsert" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json
JavaScript
fetch('[INSTANCE-URL]/_schemas/address?wm=upsert', {
method: 'PUT',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"$schema": "https://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"address": { "type": "string" },
"city": { "type": "string" },
"postal-code": { "type": "string" },
"country": { "type": "string"}
},
"required": ["address", "city", "country"]
})
})
.then(response => {
if (response.ok) {
console.log('Address schema created successfully');
} else {
console.error('Failed to create address schema:', response.status);
}
})
.catch(error => console.error('Error:', error));
This schema defines an address format that requires the address, city, and country fields.
Note
|
RESTHeart automatically generates an id property for the schema (not to be confused with the _id field).
|
cURL
curl -X GET "[INSTANCE-URL]/_schemas/address" \
-H "Authorization: Basic [BASIC-AUTH]"
HTTPie
http GET "[INSTANCE-URL]/_schemas/address" \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
fetch('[INSTANCE-URL]/_schemas/address', {
method: 'GET',
headers: {
'Authorization': 'Basic [BASIC-AUTH]'
}
})
.then(response => response.json())
.then(data => {
console.log('Retrieved address schema:', data);
})
.catch(error => console.error('Error:', error));
HTTP/1.1 200 OK
Content-Type: application/json
{
"$schema": "https://json-schema.org/draft-04/schema#",
"id": "https://schema-store/restheart/address#",
"_id": "address",
"type": "object",
"properties": {
"address": { "type": "string" },
"city": { "type": "string" },
"postal-code": { "type": "string" },
"country": { "type": "string"}
},
"required": ["address", "city", "country"]
}
Step 3: Apply the Schema to a Collection
To enforce the schema on a collection, update the collection’s metadata:
cURL
curl -X PUT "[INSTANCE-URL]/addresses" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"jsonSchema": {
"schemaId": "address"
}
}'
HTTPie
http PUT "[INSTANCE-URL]/addresses" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
jsonSchema:='{
"schemaId": "address"
}'
JavaScript
fetch('[INSTANCE-URL]/addresses', {
method: 'PUT',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"jsonSchema": {
"schemaId": "address"
}
})
})
.then(response => {
if (response.ok) {
console.log('Collection configured with schema validation');
} else {
console.error('Failed to configure collection:', response.status);
}
})
.catch(error => console.error('Error:', error));
The collection metadata’s jsonSchema
property has the following options:
Property | Description | Required |
---|---|---|
|
The _id of the JSON schema to enforce |
Yes |
|
The database containing the schema |
No (defaults to current database) |
Validating MongoDB BSON Types
MongoDB uses BSON (Binary JSON) which supports additional data types not available in standard JSON. To validate these types, you can define schema definitions for BSON types.
Example: Defining BSON Types Schema
cURL
curl -X PUT "[INSTANCE-URL]/_schemas/bson" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"_id": "bson",
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"date": {
"type": "object",
"properties": {
"$date": { "type": "number" }
},
"additionalProperties": false
},
"objectid": {
"type": "object",
"properties": {
"$oid": { "type": "string" }
},
"additionalProperties": false
}
}
}'
HTTPie
echo '{
"_id": "bson",
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"date": {
"type": "object",
"properties": {
"$date": { "type": "number" }
},
"additionalProperties": false
},
"objectid": {
"type": "object",
"properties": {
"$oid": { "type": "string" }
},
"additionalProperties": false
}
}
}' | http PUT "[INSTANCE-URL]/_schemas/bson" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json
JavaScript
fetch('[INSTANCE-URL]/_schemas/bson', {
method: 'PUT',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"_id": "bson",
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"date": {
"type": "object",
"properties": {
"$date": { "type": "number" }
},
"additionalProperties": false
},
"objectid": {
"type": "object",
"properties": {
"$oid": { "type": "string" }
},
"additionalProperties": false
}
}
})
})
.then(response => {
if (response.ok) {
console.log('BSON schema definitions created successfully');
} else {
console.error('Failed to create BSON schema:', response.status);
}
})
.catch(error => console.error('Error:', error));
Using BSON Types in Schemas
You can reference these BSON type definitions in other schemas using the $ref
keyword:
cURL
curl -X PUT "[INSTANCE-URL]/_schemas/post" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"_id": "post",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"_id": { "$ref": "http://schema-store/restheart/bson#/definitions/objectid" },
"_etag": { "$ref": "http://schema-store/restheart/bson#/definitions/objectid" },
"title": { "type": "string" },
"content": { "type": "string" },
"published": { "type": "boolean" },
"publishDate": { "$ref": "http://schema-store/restheart/bson#/definitions/date" }
},
"required": ["title", "content"]
}'
HTTPie
echo '{
"_id": "post",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"_id": { "$ref": "http://schema-store/restheart/bson#/definitions/objectid" },
"_etag": { "$ref": "http://schema-store/restheart/bson#/definitions/objectid" },
"title": { "type": "string" },
"content": { "type": "string" },
"published": { "type": "boolean" },
"publishDate": { "$ref": "http://schema-store/restheart/bson#/definitions/date" }
},
"required": ["title", "content"]
}' | http PUT "[INSTANCE-URL]/_schemas/post" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json
JavaScript
fetch('[INSTANCE-URL]/_schemas/post', {
method: 'PUT',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"_id": "post",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"_id": { "$ref": "http://schema-store/restheart/bson#/definitions/objectid" },
"_etag": { "$ref": "http://schema-store/restheart/bson#/definitions/objectid" },
"title": { "type": "string" },
"content": { "type": "string" },
"published": { "type": "boolean" },
"publishDate": { "$ref": "http://schema-store/restheart/bson#/definitions/date" }
},
"required": ["title", "content"]
})
})
.then(response => {
if (response.ok) {
console.log('Post schema with BSON references created successfully');
} else {
console.error('Failed to create post schema:', response.status);
}
})
.catch(error => console.error('Error:', error));
Testing the Validation
Let’s see validation in action by attempting to create both valid and invalid documents.
Trying to Create an Invalid Document
cURL
curl -X POST "[INSTANCE-URL]/addresses" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"address": "Via D'Annunzio 28"
}'
HTTPie
http POST "[INSTANCE-URL]/addresses" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
address="Via D'Annunzio 28"
JavaScript
fetch('[INSTANCE-URL]/addresses', {
method: 'POST',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"address": "Via D'Annunzio 28"
})
})
.then(response => {
if (response.ok) {
console.log('Document created successfully');
} else {
console.error('Validation failed:', response.status);
return response.json();
}
})
.then(errorData => {
if (errorData) console.log('Validation error:', errorData.message);
})
.catch(error => console.error('Error:', error));
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"http status code": 400,
"http status description": "Bad Request",
"message": "Request content violates schema 'address': 2 schema violations found, required key [city] not found, required key [country] not found"
}
The request is rejected because it’s missing required fields defined in the schema.
Creating a Valid Document
cURL
curl -X POST "[INSTANCE-URL]/addresses" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"address": "Via D'Annunzio, 28",
"city": "L'Aquila",
"country": "Italy",
"postal-code": "67100"
}'
HTTPie
http POST "[INSTANCE-URL]/addresses" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
address="Via D'Annunzio, 28" \
city="L'Aquila" \
country="Italy" \
postal-code="67100"
JavaScript
fetch('[INSTANCE-URL]/addresses', {
method: 'POST',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"address": "Via D'Annunzio, 28",
"city": "L'Aquila",
"country": "Italy",
"postal-code": "67100"
})
})
.then(response => {
if (response.ok) {
console.log('Valid document created successfully');
} else {
console.error('Failed to create document:', response.status);
}
})
.catch(error => console.error('Error:', error));
This document passes validation because it includes all required fields with the correct data types.
Advanced Schema Features
Composite Schemas
You can create complex validation rules by combining schemas:
{
"allOf": [
{ "$ref": "#/definitions/address" },
{ "$ref": "#/definitions/contact" }
]
}
Conditional Validation
Apply different validation rules based on document properties:
{
"if": {
"properties": { "type": { "enum": ["business"] } }
},
"then": {
"required": ["taxId", "companyName"]
},
"else": {
"required": ["firstName", "lastName"]
}
}
Limitations
The jsonSchema validator has some limitations to be aware of:
-
Bulk Operations: By default, the validator doesn’t support bulk PATCH requests:
cURL
curl -X PATCH "[INSTANCE-URL]/addresses/*?filter={\"country\":\"Italy\"}" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{ "updated": true }'
HTTPie
http PATCH "[INSTANCE-URL]/addresses/*?filter={\"country\":\"Italy\"}" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
updated:=true
JavaScript
fetch('[INSTANCE-URL]/addresses/*?filter={"country":"Italy"}', {
method: 'PATCH',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({ "updated": true })
})
.then(response => {
if (response.ok) {
console.log('Bulk update completed successfully');
} else {
console.error('Bulk update failed:', response.status);
return response.json();
}
})
.then(errorData => {
if (errorData) console.log('Error details:', errorData.message);
})
.catch(error => console.error('Error:', error));
HTTP/1.1 501 Not Implemented
Content-Type: application/json
{
"http status code": 501,
"http status description": "Not Implemented",
"message": "'jsonSchema' checker does not support bulk PATCH requests. Set 'skipNotSupported:true' to allow them."
}
To allow bulk PATCH operations without validation, add the skipNotSupported
metadata property:
cURL
curl -X PATCH "[INSTANCE-URL]/addresses" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"jsonSchema": {
"schemaId": "address",
"skipNotSupported": true
}
}'
HTTPie
http PATCH "[INSTANCE-URL]/addresses" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
jsonSchema:='{
"schemaId": "address",
"skipNotSupported": true
}'
JavaScript
fetch('[INSTANCE-URL]/addresses', {
method: 'PATCH',
headers: {
'Authorization': 'Basic [BASIC-AUTH]',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"jsonSchema": {
"schemaId": "address",
"skipNotSupported": true
}
})
})
.then(response => {
if (response.ok) {
console.log('Collection schema configuration updated');
} else {
console.error('Failed to update schema configuration:', response.status);
}
})
.catch(error => console.error('Error:', error));
Best Practices
-
Start simple: Begin with basic schemas and refine them as your application evolves
-
Reuse common patterns: Use
$ref
to reference shared definitions -
Test thoroughly: Verify both valid and invalid document scenarios
-
Use descriptive error messages: Set
errorMessage
properties to guide users -
Leverage additional keywords: Explore pattern, minimum/maximum, and other constraints for precise validation
-
Document your schemas: Include descriptions for fields to generate helpful documentation