Validating Documents with JSON Schema
RESTHeart Cloud🔧 Configuration
⚡ Setup Guide
To run the examples on this page, you need a RESTHeart instance.
Option 1: Use RESTHeart Cloud (Recommended)
The fastest way to get started is with RESTHeart Cloud. Create a free service in minutes:
-
Sign up at cloud.restheart.com
-
Create a free API service
-
Set up your root user following the Root User Setup guide
-
Use the configuration panel above to set your service URL and credentials
|
Tip
|
All code examples on this page will automatically use your configured RESTHeart Cloud credentials. |
Option 2: Run RESTHeart Locally
If you prefer local development, follow the Setup Guide to install RESTHeart on your machine.
|
Note
|
Local instances run at http://localhost:8080 with default credentials admin:secret
|
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
$refkeyword -
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 -i -X PUT "[RESTHEART-URL]/_schemas" \
-H "Authorization: Basic [BASIC-AUTH]"
HTTPie
http PUT "[RESTHEART-URL]/_schemas" \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
fetch('[RESTHEART-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 -i -X PUT "[RESTHEART-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 "[RESTHEART-URL]/_schemas/address?wm=upsert" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json
JavaScript
fetch('[RESTHEART-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 -i -X GET "[RESTHEART-URL]/_schemas/address" \
-H "Authorization: Basic [BASIC-AUTH]"
HTTPie
http GET "[RESTHEART-URL]/_schemas/address" \
Authorization:"Basic [BASIC-AUTH]"
JavaScript
fetch('[RESTHEART-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 -i -X PUT "[RESTHEART-URL]/addresses" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"jsonSchema": {
"schemaId": "address"
}
}'
HTTPie
http PUT "[RESTHEART-URL]/addresses" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
jsonSchema:='{
"schemaId": "address"
}'
JavaScript
fetch('[RESTHEART-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.
Note: Prefix Bson types with an underscore to prevent the request parser from interpreting them as actual Bson types, e.g., use _$date instead of $date.
Example: Defining BSON Types Schema
cURL
curl -i -X PUT "[RESTHEART-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 "[RESTHEART-URL]/_schemas/bson" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json
JavaScript
fetch('[RESTHEART-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 -i -X PUT "[RESTHEART-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 "[RESTHEART-URL]/_schemas/post" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json
JavaScript
fetch('[RESTHEART-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 -i -X POST "[RESTHEART-URL]/addresses" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"address": "Via D'Annunzio 28"
}'
HTTPie
http POST "[RESTHEART-URL]/addresses" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
address="Via D'Annunzio 28"
JavaScript
fetch('[RESTHEART-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 -i -X POST "[RESTHEART-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 "[RESTHEART-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('[RESTHEART-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"]
}
}
Using the JSON Schema Provider in Plugins
|
Note
|
Available from v9.7.0
Starting from RESTHeart v9.7.0, the JSON Schema store is exposed as a Provider (json-schemas), making it accessible to any plugin — not just the MongoService pipeline.
|
Previously, the schema store was only reachable from within the MongoService pipeline via JsonSchemaCacheSingleton. Any plugin that needed to validate a document against a stored schema had no supported way to access it.
The json-schemas Provider exposes the JsonSchemas interface (defined in restheart-commons):
public interface JsonSchemas {
void validate(BsonDocument doc, String schemaStoreDb, BsonValue schemaId)
throws SchemaValidationException, JsonSchemaNotFoundException;
// resolves the schema once for the whole batch
void validate(List<BsonDocument> docs, String schemaStoreDb, BsonValue schemaId)
throws SchemaValidationException, JsonSchemaNotFoundException;
String get(String schemaStoreDb, BsonValue schemaId)
throws JsonSchemaNotFoundException;
}
Documents are always rendered to JSON with RESTHeart’s default representation before being matched against the schema. The rendering does not depend on the jsonMode of the request that triggered the validation, so the same document always validates the same way — in particular, dates are rendered as {"$date": <millis>}.
Injecting the Provider
Any plugin can inject the json-schemas Provider:
@RegisterPlugin(name = "myPlugin", description = "...")
public class MyPlugin implements Service<JsonRequest, JsonResponse> {
@Inject("json-schemas")
private JsonSchemas jsonSchemas;
@Override
public void handle(JsonRequest request, JsonResponse response) {
var doc = request.getContent().asDocument();
try {
jsonSchemas.validate(doc, "mydb", new BsonString("my-schema"));
} catch (SchemaValidationException e) {
response.setInError(400, "Validation failed: " + e.getViolations());
} catch (JsonSchemaNotFoundException e) {
response.setInError(500, "Schema not found");
}
}
}
Getting the Raw Schema
The get() method returns the raw JSON schema as a string, which is useful when you need the schema for purposes other than validation (e.g., generating documentation or client-side validation):
try {
String schemaJson = jsonSchemas.get("mydb", new BsonString("my-schema"));
// parse schemaJson as needed
} catch (JsonSchemaNotFoundException e) {
// handle missing schema
}
Key Benefits
-
Platform capability: The schema store is a platform feature, not a MongoService internal
-
Single cached source: One well-known, cached way to resolve schemas instead of every plugin re-implementing loading and caching
-
No compile-time dependency on restheart-mongodb: The
JsonSchemasinterface lives inrestheart-commons, so consumers depend only on commons -
Validation library isolation: The everit validation library does not cross the module boundary
|
Important
|
The implementation still lives in If your plugin must keep working without
This is what |
JSON Schema validation on user documents
|
Note
|
Available from v9.7.0
Starting from RESTHeart v9.7.0, the restheart-accounts plugin validates user documents against the collection’s JSON Schema on creation and update.
|
When a JSON Schema is configured on the users collection (via _properties metadata), the following operations validate the user document against the schema:
-
POST /auth/register— user creation -
DbHelper.updateUser()andDbHelper.unsetUserFields()— all user update paths (password reset, email verification, team operations, etc.)
This ensures that invariants declared on the users collection — for example that a consents field conforms to a specific structure when present — are enforced even for documents created or modified through restheart-accounts endpoints, which bypass the MongoService pipeline.
When a jsonSchema is configured, additional body properties sent to POST /auth/register are carried into the user document before validation. This allows clients to submit application-level fields (such as consents) that the schema can validate. Service-managed fields (_id, password, roles, etc.) are never overwritten by the body. When no schema is configured, unmapped body fields are dropped as before.
|
Note
|
Application-level fields such as consents should be declared optional in the schema. Their absence is enforced by guard rules at the application level, not by the schema. See Gating on Consents for the full pattern.
|
Validation is opt-in: no jsonSchema metadata on the collection means no validation, exactly as for any other collection.
See User Registration — JSON Schema validation for details and examples.
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 -i -X PATCH "[RESTHEART-URL]/addresses/*" \
-G --data-urlencode 'filter={"country":"Italy"}' \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{ "updated": true }'
HTTPie
http PATCH "[RESTHEART-URL]/addresses/*?filter={\"country\":\"Italy\"}" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
updated:=true
JavaScript
fetch('[RESTHEART-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 -i -X PATCH "[RESTHEART-URL]/addresses" \
-H "Authorization: Basic [BASIC-AUTH]" \
-H "Content-Type: application/json" \
-d '{
"jsonSchema": {
"schemaId": "address",
"skipNotSupported": true
}
}'
HTTPie
http PATCH "[RESTHEART-URL]/addresses" \
Authorization:"Basic [BASIC-AUTH]" \
Content-Type:application/json \
jsonSchema:='{
"schemaId": "address",
"skipNotSupported": true
}'
JavaScript
fetch('[RESTHEART-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
$refto reference shared definitions -
Test thoroughly: Verify both valid and invalid document scenarios
-
Use descriptive error messages: Set
errorMessageproperties 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