Skip to main content
Resolvers transform and compute data properties for Feathers services, enabling virtual properties, data sanitization, and context-aware transformations.

resolve

Creates a resolver instance that resolves properties based on property resolver functions.
import { resolve, virtual } from '@feathersjs/schema'

const userResolver = resolve<User, HookContext>({
  password: async () => undefined,
  name: virtual(async (user) => `${user.firstName} ${user.lastName}`)
})

Signatures

function resolve<T, C>(
  properties: PropertyResolverMap<T, C>,
  options?: ResolverOptions<T, C>
): Resolver<T, C>

function resolve<T, C>(options: ResolverConfig<T, C>): Resolver<T, C>

Parameters

properties
PropertyResolverMap<T, C>
required
Object mapping property names to resolver functions
options
ResolverOptions<T, C>
Optional configuration object
ResolverOptions:
schema
Schema<T>
Schema for validation. Can be used with validate option
converter
ResolverConverter<T, C>
Function to transform data before property resolution
validate
'before' | 'after' | false
Deprecated. When to validate: before or after resolution. Use validateData hook instead

Returns

Returns a Resolver<T, C> instance.

Resolver

The resolver class that handles property resolution.

resolve

Resolves all properties for the given data:
const resolved = await userResolver.resolve(data, context)
data
D
required
Data object to resolve
context
C
required
Context object (typically HookContext)
status
Partial<ResolverStatus<T, C>>
Optional resolver status for nested resolution
Returns: Promise<T> - Resolved data object

resolveProperty

Resolves a single property:
const name = await userResolver.resolveProperty('name', data, context)
name
keyof T
required
Property name to resolve
data
D
required
Data object containing the property
context
C
required
Context object
status
Partial<ResolverStatus<T, C>>
Optional resolver status
Returns: Promise<T[K]> - Resolved property value

convert

Converts data using the configured converter:
const converted = await userResolver.convert(data, context)
data
D
required
Data to convert
context
C
required
Context object
status
Partial<ResolverStatus<T, C>>
Optional resolver status
Returns: Promise<T | undefined> - Converted data

Property Resolvers

Property resolver functions transform individual properties.

PropertyResolver

Type signature for property resolvers:
type PropertyResolver<T, V, C> = (
  value: V | undefined,
  obj: T,
  context: C,
  status: ResolverStatus<T, C>
) => Promise<V | undefined> | V | undefined
value
V | undefined
Current value of the property
obj
T
The entire data object being resolved
context
C
Context object (e.g., HookContext)
status
ResolverStatus<T, C>
Current resolution status with path and stack information
Returns: Resolved value or undefined to remove the property

Example

const userResolver = resolve<User, HookContext>({
  // Remove sensitive data
  password: async () => undefined,
  
  // Transform existing value
  email: async (value) => value?.toLowerCase(),
  
  // Compute from other properties
  fullName: async (value, user) => `${user.firstName} ${user.lastName}`,
  
  // Use context
  isOwner: async (value, user, context) => {
    return context.params.user?.id === user.id
  }
})

virtual

Creates a resolver for a computed property that has no initial value.
const resolver = resolve<User, HookContext>({
  name: virtual(async (user) => `${user.firstName} ${user.lastName}`)
})

Parameters

virtualResolver
VirtualResolver<T, V, C>
required
Function to compute the virtual property
VirtualResolver signature:
type VirtualResolver<T, V, C> = (
  obj: T,
  context: C,
  status: ResolverStatus<T, C>
) => Promise<V | undefined> | V | undefined

Returns

Returns a PropertyResolver<T, V, C> marked as virtual.

Types

ResolverStatus

Tracking object passed through resolution:
interface ResolverStatus<T, C> {
  path: string[]              // Current resolution path
  originalContext?: C         // Original context from hook
  properties?: (keyof T)[]    // Specific properties to resolve
  stack: PropertyResolver[]   // Resolver call stack (prevents circular dependencies)
}

ResolverConverter

Function to convert data before property resolution:
type ResolverConverter<T, C> = (
  obj: any,
  context: C,
  status: ResolverStatus<T, C>
) => Promise<T | undefined> | T | undefined

PropertyResolverMap

Object mapping properties to their resolvers:
type PropertyResolverMap<T, C> = {
  [key in keyof T]?: PropertyResolver<T, T[key], C> | ReturnType<typeof virtual>
}

Examples

Basic Property Resolution

import { resolve } from '@feathersjs/schema'
import type { HookContext } from '@feathersjs/feathers'

interface User {
  id: number
  email: string
  password: string
  firstName: string
  lastName: string
  fullName: string
}

const userResolver = resolve<User, HookContext>({
  password: async () => undefined,
  fullName: async (value, user) => `${user.firstName} ${user.lastName}`
})

const result = await userResolver.resolve(
  {
    id: 1,
    email: '[email protected]',
    password: 'secret',
    firstName: 'John',
    lastName: 'Doe'
  },
  context
)
// result: { id: 1, email: '...', firstName: 'John', lastName: 'Doe', fullName: 'John Doe' }

Virtual Properties

import { resolve, virtual } from '@feathersjs/schema'

const userResolver = resolve<User, HookContext>({
  // Virtual property - only computed, never has initial value
  gravatar: virtual(async (user) => {
    const hash = md5(user.email.toLowerCase())
    return `https://www.gravatar.com/avatar/${hash}`
  })
})

Context-Aware Resolution

const messageResolver = resolve<Message, HookContext>({
  userId: async (value, message, context) => {
    // Associate with authenticated user
    return context.params.user?.id
  },
  
  canEdit: virtual(async (message, context) => {
    const user = context.params.user
    return user?.id === message.userId || user?.isAdmin
  })
})

With Data Converter

import { ObjectId } from 'mongodb'

const messageResolver = resolve<Message, HookContext>({
  properties: {
    text: async (value) => value?.trim()
  },
  converter: async (data) => {
    // Convert MongoDB ObjectId to string
    return {
      ...data,
      id: data._id?.toString(),
      userId: data.userId?.toString()
    }
  }
})

Selective Property Resolution

// Resolve only specific properties
const result = await userResolver.resolve(
  data,
  context,
  { properties: ['fullName', 'email'] }
)
// Only resolves and returns fullName and email

Nested Resolution

const postResolver = resolve<Post, HookContext>({
  author: async (value, post, context) => {
    const user = await context.app.service('users').get(post.authorId)
    // Resolve nested user object
    return userResolver.resolve(user, context)
  }
})

Build docs developers (and LLMs) love