What is @sanity/schema?
@sanity/schema is a package used to define and manage schemas for Sanity, a platform for structured content. It allows developers to create and validate content models, which can then be used to structure and manage content in a Sanity project.
What are @sanity/schema's main functionalities?
Define Document Schema
This feature allows you to define a document schema with various fields. In this example, a 'post' document type is defined with 'title' and 'body' fields.
const schema = require('@sanity/schema');
const mySchema = schema.compile({
name: 'mySchema',
types: [
{
name: 'post',
type: 'document',
fields: [
{name: 'title', type: 'string'},
{name: 'body', type: 'text'}
]
}
]
});
Define Object Schema
This feature allows you to define an object schema. In this example, an 'author' object type is defined with 'name' and 'bio' fields.
const schema = require('@sanity/schema');
const mySchema = schema.compile({
name: 'mySchema',
types: [
{
name: 'author',
type: 'object',
fields: [
{name: 'name', type: 'string'},
{name: 'bio', type: 'text'}
]
}
]
});
Validation
This feature allows you to add validation rules to your schema fields. In this example, the 'title' field is required and must be at least 10 characters long, while the 'body' field is also required.
const schema = require('@sanity/schema');
const mySchema = schema.compile({
name: 'mySchema',
types: [
{
name: 'post',
type: 'document',
fields: [
{name: 'title', type: 'string', validation: Rule => Rule.required().min(10)},
{name: 'body', type: 'text', validation: Rule => Rule.required()}
]
}
]
});
Other packages similar to @sanity/schema
mongoose
Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It provides a schema-based solution to model your application data. Unlike @sanity/schema, which is specific to Sanity, Mongoose is used for MongoDB and offers features like schema validation, middleware, and more.
ajv
Ajv is a JSON schema validator for JavaScript. It allows you to define and validate JSON schemas, similar to how @sanity/schema allows you to define and validate content schemas. Ajv is more general-purpose and can be used in various contexts beyond content management.
joi
Joi is a powerful schema description language and data validator for JavaScript. It allows you to create blueprints for JavaScript objects and validate them. While @sanity/schema is tailored for Sanity content models, Joi is more versatile and can be used for general data validation.
Sanity schema
Terminology
Schema
A collection of typesType
A specification of a data structure. Available through schema lookup.Member type
A member type is a type contained by a schema type. For example, an array may specify the allowed item types by defining members types from schema types. A reference may be a reference to a set of other types. A member type is not added to the schema and is not available through schema lookup, but rather exists as a property of the owner type.
Constraints
No inheritance
You are almost always better off using composition, rather than inheritance hierarchies
E.g.:
const PERSON = {
type: 'object',
name: 'person',
fields: [
{name: 'firstName', type: 'string'},
{
name: 'address',
type: 'object',
fields: [
{
name: 'street',
type: 'string'
}
]
}
]
}
If one were to introduce a user type, it would be tempting to think of it as a subtype of person, adding a few additional fields specific for the user type, like this:
const USER = {
name: 'user',
type: 'person',
fields: [
{
name: 'username',
type: 'string'
}
]
}
A problem with the above is: how do we merge the fields? Should the fields from PERSON
be placed before the fields from USER
? What if both types define the same field, should the subtype override? What if that field is an object where we'd like to keep some of the fields, but remove others? It quite quickly becomes messy.
A better solution would be to define common fields outside, and re-use them across types:
e.g.:
const FIRST_NAME_FIELD = {name: 'firstName', type: 'string'}
const ADDRESS_FIELD = {
name: 'address',
type: 'object',
fields: [
{
name: 'zip',
type: 'string'
},
{
name: 'street',
type: 'string'
},
{
name: 'city',
type: 'string'
}
],
}
const PERSON = {
type: 'object',
name: 'person',
fields: [
FIRST_NAME_FIELD,
ADDRESS_FIELD
]
}
const USER = {
type: 'object',
name: 'person',
fields: [
FIRST_NAME_FIELD,
{name: 'username', type: 'string'},
ADDRESS_FIELD
]
}
You could even take this further by extracting the individual fields of the address
type and compose in different ways.