Skip to content

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:

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

ts
// 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:

ts
// 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:

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

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

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

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

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

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

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

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

Released under the MIT License.