JSON Schema
As an alternative to TypeBox, @feathersjs/schema
also provides the ability to define plain JSON schemas as objects. It uses json-schema-to-ts to turn those schemas into TypeScript types.
Need JSON Schema help?
You can find an introduction in the JSON schema official getting started guide and a lot of type-specific JSON Schema examples in the json-schema-to-ts docs.
Creating Schemas
Definitions
If you are not familiar with JSON schema have a look at the official getting started guide. Here is an example for a possible user schema:
import type { FromSchema } from '@feathersjs/schema'
export const userSchema = {
$id: 'User',
type: 'object',
additionalProperties: false,
required: ['email', 'password'],
properties: {
id: { type: 'number' },
email: { type: 'string' },
password: { type: 'string' }
}
} as const
export type User = FromSchema<typeof userSchema>
Generating Correct Types
For correct TypeScript types, the definition always needs to be declared as const
. This first example will not produce correct types because the definition is not immediately followed by as const
:
// Will not produce correct types.
const definition = { type: 'object' } // `as const` is missing, here.
This next example does declare as const
after the definition
, so the types will be generated correctly:
// Produces correct types.
const definition = { type: 'object' } as const
Extending Schemas
To create a new schema that extends an existing one, combine the schema properties (and schema.required
, if used) with the new properties:
import type { FromSchema } from '@feathersjs/schema'
export const userDataSchema = {
$id: 'User',
type: 'object',
additionalProperties: false,
required: ['email', 'password'],
properties: {
email: { type: 'string' },
password: { type: 'string' }
}
} as const
export type UserData = FromSchema<typeof userDataSchema>
export const userSchema = {
$id: 'UserResult',
type: 'object',
additionalProperties: false,
required: [...userDataSchema.required, 'id'],
properties: {
...userDataSchema.properties,
id: { type: 'number' }
}
} as const
export type User = FromSchema<typeof userSchema>
References
Associated schemas can be initialized via the $ref
keyword referencing the $id
set during schema definition.
In TypeScript, the referenced type needs to be added explicitly.
import type { FromSchema } from '@feathersjs/schema'
export const userSchema = {
$id: 'User',
type: 'object',
additionalProperties: false,
required: ['email', 'password'],
properties: {
id: { type: 'number' },
email: { type: 'string' },
password: { type: 'string' }
}
} as const
export type User = FromSchema<typeof userSchema>
export const messageSchema = {
$id: 'Message',
type: 'object',
additionalProperties: false,
required: ['text'],
properties: {
text: { type: 'string' },
user: { $ref: 'User' }
}
} as const
export type Message = FromSchema<
typeof messageSchema,
{
// All schema references need to be passed to get the correct type
references: [typeof userSchema]
}
>
Query Helpers
Schema ships with a few helpers to automatically create schemas that comply with the Feathers query syntax (like $gt
, $ne
etc.):
querySyntax
querySyntax(schema.properties, extensions)
initializes all properties the additional query syntax properties $limit
, $skip
, $select
and $sort
. $select
and $sort
will be typed so they only allow existing schema properties.
import { querySyntax } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'
export const userQuerySchema = {
$id: 'UserQuery',
type: 'object',
additionalProperties: false,
properties: {
...querySyntax(userSchema.properties)
}
} as const
export type UserQuery = FromSchema<typeof userQuerySchema>
const userQuery: UserQuery = {
$limit: 10,
$select: ['email', 'id'],
$sort: {
email: 1
}
}
Additional special query properties that are not already included in the query syntax like $ilike
can be added like this:
import { querySyntax } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'
export const userQuerySchema = {
$id: 'UserQuery',
type: 'object',
additionalProperties: false,
properties: {
...querySyntax(userSchema.properties, {
email: {
$ilike: {
type: 'string'
}
}
} as const)
}
} as const
export type UserQuery = FromSchema<typeof userQuerySchema>
const userQuery: UserQuery = {
$limit: 10,
$select: ['email', 'id'],
$sort: {
email: 1
},
email: {
$ilike: '%@example.com'
}
}
queryProperty
queryProperty
helper takes a definition for a single property and returns a schema that allows the default query operators. This helper supports the operators listed, below. Learn what each one means in the common query operator documentation.
$gt
$gte
$lt
$lte
$ne
$in
$nin
The name
property in the example, below, shows how queryProperty
wraps a single property's definition.
import { queryProperty } from '@feathersjs/schema'
export const userQuerySchema = {
$id: 'UserQuery',
type: 'object',
additionalProperties: false,
properties: {
name: queryProperty({ type: 'string' })
}
} as const
With the queryProperty
utility in place, the schema will allow querying on name
using any of the above-listed operators. With it in place, the query in the following example will not throw an error:
const query = { name: { $in: ['Marco', 'Polo'] } }
app.service('users').find({ query })
You can learn how it works, here.
queryProperties
queryProperties(schema.properties)
takes the all properties of a schema and converts them into query schema properties (using queryProperty
)
Validators
The following functions are available to get a validator function from a JSON schema definition.
note
See the validators chapter for more information on validators and validator functions.
getDataValidator
getDataValidator(definition, validator)
returns validators for the data of create
, update
and patch
service methods. You can either pass a single definition in which case all properties of the patch
schema will be optional or individual validators for create
, update
and patch
.
import { getDataValidator, Ajv } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'
const userDataSchema = {
$id: 'User',
type: 'object',
additionalProperties: false,
required: ['email', 'password'],
properties: {
email: { type: 'string' },
password: { type: 'string' }
}
} as const
type UserData = FromSchema<typeof userDataSchema>
const dataValidator = new Ajv()
const dataValidator = getDataValidator(userDataSchema, dataValidator)
getValidator
getValidator(definition, validator)
returns a single validator function for a JSON schema.
import { querySyntax, Ajv, getValidator } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'
export const userQuerySchema = {
$id: 'UserQuery',
type: 'object',
additionalProperties: false,
properties: {
...querySyntax(userSchema.properties)
}
} as const
export type UserQuery = FromSchema<typeof userQuerySchema>
// Since queries can be only strings we can to coerce them
const queryValidator = new Ajv({
coerceTypes: true
})
const messageValidator = getValidator(userQuerySchema, queryValidator)