feathers/docs/api/schema/schema.md
Marshall Thompson ae85fa216f
feat(docs): New website and documentation pages (#2802)
* feat(docs) new docs site started

* Minor page edits

* feat(footer) fix spacing

* empty guides template

Co-authored-by: daffl <daff@neyeon.com>
2022-10-17 13:05:01 -06:00

6.6 KiB

Schemas

schema is a small wrapper over JSON schema, AJV and json-schema-to-ts to define data schemas. It can also automatically get the correct TypeScript type for a schema. This allows for a single place to define your types and validation rules in plain JavaScript or TypeScript which can then be used by many other parts of a Feathers application. Schemas are also used by resolvers to validate and convert data before or after dynamically resolving properties.

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:

:::: tabs :options="{ useUrlFragment: false }"

::: tab "JavaScript"

import { schema } from '@feathersjs/schema';

export const userSchema = schema({
  $id: 'User',
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    id: { type: 'number' },
    email: { type: 'string' },
    password: { type: 'string' }
  }
});

:::

::: tab "TypeScript"

import { HookContext } from './definitions';
import { schema, Infer } from '@feathersjs/schema';

export const userSchema = schema({
  $id: 'User',
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    id: { type: 'number' },
    email: { type: 'string' },
    password: { type: 'string' }
  }
} as const);

export type User = Infer<typeof userSchema>;

:::

::::

Very Important: To get the correct TypeScript types the definition always needs to be declared via schema({} as const).

Options

schema(definition, ajv) allows to initialize a schema with a custom AJV instance:

import ajvErrors from 'ajv-errors';
import Ajv form 'ajv';
import { schema } from '@feathersjs/schema';

const ajv = new Ajv({
  coerceTypes: true
});

ajvErrors(ajv);

export const userSchema = schema({
  $id: 'User',
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    id: { type: 'number' },
    email: { type: 'string' },
    password: { type: 'string' }
  }
}, ajv);

Extension

To create a new schema that extends an existing one, combine the schema properties from schema.properties (an schema.required if needed) with the new properties:

:::: tabs :options="{ useUrlFragment: false }"

::: tab "JavaScript"

import { schema } from '@feathersjs/schema';

export const userSchema = schema({
  $id: 'User',
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    id: { type: 'number' },
    email: { type: 'string' },
    password: { type: 'string' }
  }
});

// The user result has all properties from the user but also an
// additional `id` added by the database
export const userResultSchema = schema({
  $id: 'UserResult',
  type: 'object',
  additionalProperties: false,
  required: [...userSchema.required, 'id'],
  properties: {
    ...userSchema.properties,
    id: { type: 'number' }
  }
});

:::

::: tab "TypeScript"

import { HookContext } from './definitions';
import { schema, Infer } from '@feathersjs/schema';

export const userSchema = schema({
  $id: 'User',
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    email: { type: 'string' },
    password: { type: 'string' }
  }
} as const);

export type User = Infer<typeof userSchema>;

export const userResultSchema = schema({
  $id: 'UserResult',
  type: 'object',
  additionalProperties: false,
  required: [...userSchema.required, 'id'],
  properties: {
    ...userSchema.properties,
    id: { type: 'number' }
  }
});

export type User = Infer<typeof userResultSchema>;

:::

::::

Associations

Associated schemas can be initialized via the $ref keyword referencing the $id set during schema definition.

:::: tabs :options="{ useUrlFragment: false }"

::: tab "JavaScript"

import { schema } from '@feathersjs/schema';

export const userSchema = schema({
  $id: 'User',
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    id: { type: 'number' },
    email: { type: 'string' },
    password: { type: 'string' }
  }
});

export const messageSchema = schema({
  $id: 'Message',
  type: 'object',
  additionalProperties: false,
  required: ['text'],
  properties: {
    text: { type: 'string' },
    user: { $ref: 'User' }
  }
});

:::

::: tab "TypeScript" In TypeScript the referenced type needs to be added explicitly.

import { HookContext } from './definitions';
import { schema, Infer } from '@feathersjs/schema';

export const userSchema = schema({
  $id: 'User',
  type: 'object',
  additionalProperties: false,
  required: ['email', 'password'],
  properties: {
    id: { type: 'number' },
    email: { type: 'string' },
    password: { type: 'string' }
  }
});

export type User = Infer<typeof userSchema>;

export const messageSchema = schema({
  $id: 'Message',
  type: 'object',
  additionalProperties: false,
  required: ['text'],
  properties: {
    text: { type: 'string' },
    user: { $ref: 'User' }
  }
});

export type Message = Infer<typeof messageSchema> & {
  user: User
}

:::

::::

Query helper

Schema ships with the following helpers to automatically create schemas that follow the Feathers query syntax (like $gt, $ne etc.):

  • queryProperty helper takes a definition for a single property (usually { type: '<type>' }) and returns a schema that allows the default query operators
  • queryProperties(schema.properties) takes the all properties of a schema and converts them into query schema properties (using queryProperty)
  • querySyntax(schema.properties) 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';

export const userQuerySchema = schema({
  $id: 'UserQuery',
  type: 'object',
  additionalProperties: false,
  properties: {
    ...querySyntax(userSchema.properties)
  }
} as const);

export type UserQuery = Infer<typeof userQuerySchema>

const userQuery: UserQuery = {
  $limit: 10,
  $select: [ 'email', 'id' ],
  $sort: {
    email: 1
  }
}

Validation hooks

Schemas will be used for validation when they are passed to a Resolver. See the Feathers resolver on how to use the schema with resolvers.