Edit Page

Validating Documents with JSON Schema

RESTHeart Cloud

🔧 Configuration

Sets localhost:8080 with admin:secret
Values are saved in your browser

⚡ 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:

  1. Sign up at cloud.restheart.com

  2. Create a free API service

  3. Set up your root user following the Root User Setup guide

  4. 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:

  1. MongoDB’s built-in schema validation: Available in MongoDB 3.6+ using JSON Schema

  2. 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 -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

schemaId

The _id of the JSON schema to enforce

Yes

schemaStoreDb

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 JsonSchemas interface lives in restheart-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 restheart-mongodb, so the json-schemas provider only exists where that module is deployed. @Inject("json-schemas") is a hard dependency: if the provider is missing, the plugin declaring the injection is disabled at startup, not merely deprived of validation.

If your plugin must keep working without restheart-mongodb, resolve the provider optionally through the registry instead:

@Inject("registry")
private PluginsRegistry registry;

private JsonSchemas jsonSchemas;   // null when restheart-mongodb is not deployed

@OnInit
public void onInit() {
    this.jsonSchemas = registry.getProviders().stream()
        .filter(pd -> "json-schemas".equals(pd.getName()))
        .filter(pd -> pd.isEnabled())
        .map(pd -> pd.getInstance())
        .filter(p -> JsonSchemas.class.getName().equals(p.rawType().getName()))
        .map(p -> (JsonSchemas) p.get(null))
        .findFirst()
        .orElse(null);
}

This is what restheart-accounts does for the registration endpoint: the endpoint stays available, and a request is rejected only if a schema is actually configured and cannot be applied.

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() and DbHelper.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

  1. Start simple: Begin with basic schemas and refine them as your application evolves

  2. Reuse common patterns: Use $ref to reference shared definitions

  3. Test thoroughly: Verify both valid and invalid document scenarios

  4. Use descriptive error messages: Set errorMessage properties to guide users

  5. Leverage additional keywords: Explore pattern, minimum/maximum, and other constraints for precise validation

  6. Document your schemas: Include descriptions for fields to generate helpful documentation