Schemas
schema
is a small wrapper around three existing projects:
- JSON schema for defining schemas.
- AJV for validating objects with those schemas.
- json-schema-to-ts to convert those schemas to TypeScript types.
This package essentially 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.
Need JSON Schema help?
You can find a lot of type-specific JSON Schema examples in the json-schema-to-ts docs.
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 { 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>
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>
import { HookContext } from './definitions.js'
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' }
}
})
import { HookContext } from './definitions.js'
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' }
}
})
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' }
}
})
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' }
}
})
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);
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:
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>
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>
import { HookContext } from './definitions.js'
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' }
}
})
export const userResultSchema = schema({
$id: 'UserResult',
type: 'object',
additionalProperties: false,
required: [...userSchema.required, 'id'],
properties: {
...userSchema.properties,
id: { type: 'number' }
}
})
import { HookContext } from './definitions.js'
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' }
}
})
export const userResultSchema = schema({
$id: 'UserResult',
type: 'object',
additionalProperties: false,
required: [...userSchema.required, 'id'],
properties: {
...userSchema.properties,
id: { type: 'number' }
}
})
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' }
}
})
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' }
}
})
Associations
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 { 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
}
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
}
import { HookContext } from './definitions.js'
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 const messageSchema = schema({
$id: 'Message',
type: 'object',
additionalProperties: false,
required: ['text'],
properties: {
text: { type: 'string' },
user: { $ref: 'User' }
}
})
import { HookContext } from './definitions.js'
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 const messageSchema = schema({
$id: 'Message',
type: 'object',
additionalProperties: false,
required: ['text'],
properties: {
text: { type: 'string' },
user: { $ref: 'User' }
}
})
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' }
}
})
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' }
}
})
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 operatorsqueryProperties(schema.properties)
takes the all properties of a schema and converts them into query schema properties (usingqueryProperty
)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
}
}
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.