Edit Page

Validating Documents with JSON Schema

🔧 Configuration

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

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

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.

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

  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