The TypeBox integration provides type-safe schema definitions with Feathers-specific utilities for query syntax and validation.
Installation
npm install @feathersjs/typebox @sinclair/typebox
getValidator
Creates a validation function from a TypeBox schema.
import { Type, getValidator } from '@feathersjs/typebox'
import { Ajv } from '@feathersjs/schema'
const userSchema = Type.Object({
email: Type.String({ format: 'email' }),
age: Type.Number({ minimum: 0 })
})
const ajv = new Ajv()
const validate = getValidator(userSchema, ajv)
const user = await validate({ email: '[email protected]', age: 25 })
Parameters
schema
TObject | TIntersect | TUnion<TObject[]> | TRecord
required
TypeBox schema definition
Returns
Validator<T, R> - Async validation function that returns validated data or throws BadRequest
getDataValidator
Creates validation functions for create, update, and patch service methods.
import { Type, getDataValidator } from '@feathersjs/typebox'
import { Ajv } from '@feathersjs/schema'
const userSchema = Type.Object({
email: Type.String({ format: 'email' }),
password: Type.String({ minLength: 8 })
})
const ajv = new Ajv()
const validators = getDataValidator(userSchema, ajv)
// validators.create - requires all fields
// validators.update - requires all fields
// validators.patch - no required fields
Parameters
def
TObject | TDataSchemaMap
required
Single schema or map of schemas for each method
TDataSchemaMap:
type TDataSchemaMap = {
create: TObject
update?: TObject
patch?: TObject
}
Returns
DataValidatorMap - Object with create, update, and patch validators
Behavior
- If only
create is provided, update uses the same schema
patch uses create schema with no required fields
- Schemas are automatically given IDs with
Update and Patch suffixes
StringEnum
Creates a string enum schema from an array of allowed values.
import { StringEnum } from '@feathersjs/typebox'
const roleSchema = StringEnum(['admin', 'user', 'guest'])
const userSchema = Type.Object({
name: Type.String(),
role: StringEnum(['admin', 'user', 'guest'], { default: 'user' })
})
Parameters
Array of allowed string values
Optional configuration with default value
Returns
TypeBox schema with string enum constraint
Query Syntax Utilities
Utilities to create Feathers query syntax schemas with operators like $gt, $in, etc.
queryProperty
Creates query syntax schema for a single property.
import { Type, queryProperty } from '@feathersjs/typebox'
const ageQuery = queryProperty(
Type.Number(),
{ $regex: Type.String() } // Additional operators
)
// Allows: age: 25 or age: { $gt: 18, $lt: 65 }
TypeBox schema for the property
extension
{ [key: string]: TSchema }
default:"{}"
Additional query operators to support
Returns: Optional union of direct value or operators object
Supported operators:
$gt, $gte - Greater than (or equal)
$lt, $lte - Less than (or equal)
$ne - Not equal
$in, $nin - In/not in array
queryProperties
Creates query syntax schemas for multiple properties.
import { Type, queryProperties } from '@feathersjs/typebox'
const userType = Type.Object({
name: Type.String(),
age: Type.Number(),
email: Type.String()
})
const queryProps = queryProperties(userType, {
name: { $regex: Type.String() },
email: { $regex: Type.String() }
})
TypeBox object with properties to create queries for
extensions
{ [K in keyof T]?: { [key: string]: TSchema } }
default:"{}"
Per-property additional operators
Returns: Optional object with query syntax for each property
querySyntax
Creates complete Feathers query schema including $limit, $skip, $sort, $select, $or, and $and.
import { Type, querySyntax } from '@feathersjs/typebox'
const userType = Type.Object({
id: Type.Number(),
name: Type.String(),
age: Type.Number(),
email: Type.String()
})
const userQuerySchema = querySyntax(userType)
// Supports full Feathers query syntax:
// {
// name: 'John',
// age: { $gte: 18 },
// $limit: 10,
// $skip: 0,
// $sort: { name: 1 },
// $select: ['name', 'email'],
// $or: [{ age: { $lt: 18 } }, { age: { $gt: 65 } }]
// }
TypeBox object defining queryable properties
extensions
{ [K in keyof T]?: { [key: string]: TSchema } }
default:"{}"
Additional operators per property
options
ObjectOptions
default:"{ additionalProperties: false }"
TypeBox object options
Returns: TypeBox intersection with query operators and property queries
Query operators:
Maximum number of results (minimum: 0)
Number of results to skip (minimum: 0)
$sort
{ [K in keyof T]?: 1 | -1 }
Sort order for properties (1 = ascending, -1 = descending)
Array of property names to include in results
Array of query objects (OR logic)
$and
Array<PropertyQuery | { $or }>
Array of query objects (AND logic)
sortDefinition
Creates the $sort schema for an object.
import { Type, sortDefinition } from '@feathersjs/typebox'
const userType = Type.Object({
name: Type.String(),
age: Type.Number()
})
const sortSchema = sortDefinition(userType)
// Allows: { name: 1, age: -1 }
TypeBox object to create sort schema for
Returns: Object schema with optional integer properties (values: -1 or 1)
ObjectIdSchema
Schema for MongoDB ObjectId that accepts string or object formats.
import { ObjectIdSchema } from '@feathersjs/typebox'
const messageSchema = Type.Object({
_id: ObjectIdSchema(),
userId: ObjectIdSchema(),
text: Type.String()
})
Returns: Union of string with objectid: true or object with additional properties
Complete Example
import { Type, Static, getDataValidator, querySyntax, StringEnum } from '@feathersjs/typebox'
import { Ajv } from '@feathersjs/schema'
// Define user schema
const userSchema = Type.Object(
{
id: Type.Number(),
email: Type.String({ format: 'email' }),
password: Type.String({ minLength: 8 }),
role: StringEnum(['admin', 'user', 'guest'], { default: 'user' }),
age: Type.Optional(Type.Number({ minimum: 0, maximum: 120 })),
createdAt: Type.String({ format: 'date-time' })
},
{ $id: 'User', additionalProperties: false }
)
type User = Static<typeof userSchema>
// Create data validators
const ajv = new Ajv({ coerceTypes: true })
const userDataValidator = getDataValidator(
{
create: Type.Object({
email: Type.String({ format: 'email' }),
password: Type.String({ minLength: 8 }),
role: Type.Optional(StringEnum(['admin', 'user', 'guest']))
}),
patch: Type.Partial(Type.Object({
email: Type.String({ format: 'email' }),
password: Type.String({ minLength: 8 }),
role: StringEnum(['admin', 'user', 'guest'])
}))
},
ajv
)
// Create query schema
const userQueryProperties = Type.Pick(userSchema, ['id', 'email', 'role', 'age'])
const userQuerySchema = querySyntax(userQueryProperties)
type UserQuery = Static<typeof userQuerySchema>
// Use in service
app.service('users').hooks({
before: {
find: [hooks.validateQuery(getValidator(userQuerySchema, ajv))],
create: [hooks.validateData(userDataValidator)],
patch: [hooks.validateData(userDataValidator)]
}
})
// Example queries
const query1: UserQuery = {
age: { $gte: 18, $lte: 65 },
role: { $in: ['admin', 'user'] },
$limit: 10,
$sort: { createdAt: -1 }
}
const query2: UserQuery = {
$or: [
{ email: '[email protected]' },
{ role: 'admin' }
],
$select: ['id', 'email', 'role']
}
Type Inference
Use Static from TypeBox for type inference:
import { Type, Static } from '@feathersjs/typebox'
const messageSchema = Type.Object({
id: Type.Number(),
text: Type.String(),
userId: Type.Number()
})
type Message = Static<typeof messageSchema>
const message: Message = {
id: 1,
text: 'Hello',
userId: 123
}
Re-exports
All TypeBox types and utilities are re-exported:
import { Type, Static, TObject, TString, TNumber } from '@feathersjs/typebox'
// Equivalent to:
import { Type, Static, TObject, TString, TNumber } from '@sinclair/typebox'