Services are the heart of Feathers. They provide a uniform interface for accessing and manipulating data, whether from a database, API, or any other source.
Service Interface
A Feathers service implements up to six standard methods:
interface Service {
find ( params ?: Params ) : Promise < Result | Result []>
get ( id : Id , params ?: Params ) : Promise < Result >
create ( data : Data , params ?: Params ) : Promise < Result >
update ( id : Id , data : Data , params ?: Params ) : Promise < Result >
patch ( id : Id | null , data : Data , params ?: Params ) : Promise < Result | Result []>
remove ( id : Id | null , params ?: Params ) : Promise < Result | Result []>
}
Services are not required to implement all methods. Implement only what your use case needs.
Standard Methods
find(params)
Retrieve multiple records, optionally filtered and paginated:
const messagesService = {
async find ( params ) {
const { query = {} } = params
// Return paginated results
return {
total: 100 ,
limit: 10 ,
skip: 0 ,
data: [
{ id: 1 , text: 'Hello' },
{ id: 2 , text: 'World' }
]
}
}
}
app . use ( 'messages' , messagesService )
// Usage
const result = await app . service ( 'messages' ). find ({
query: {
$limit: 10 ,
$skip: 0 ,
read: false
}
})
interface Paginated < T > {
total : number
limit : number
skip : number
data : T []
}
const messagesService = {
async find ( params ) {
return {
total: 100 ,
limit: params . query ?. $limit || 10 ,
skip: params . query ?. $skip || 0 ,
data: await fetchMessages ( params . query )
}
}
}
const messagesService = {
async find ( params ) {
// Return array directly
return [
{ id: 1 , text: 'Hello' },
{ id: 2 , text: 'World' }
]
}
}
get(id, params)
Retrieve a single record by its unique identifier:
const usersService = {
async get ( id , params ) {
const user = await database . users . findById ( id )
if ( ! user ) {
throw new NotFound ( `User ${ id } not found` )
}
return user
}
}
app . use ( 'users' , usersService )
// Usage
const user = await app . service ( 'users' ). get ( 123 )
create(data, params)
Create one or more new records:
const usersService = {
async create ( data , params ) {
// Single record
if ( ! Array . isArray ( data )) {
const user = await database . users . insert ( data )
return user
}
// Multiple records
const users = await database . users . insertMany ( data )
return users
}
}
// Usage - single
const user = await app . service ( 'users' ). create ({
email: '[email protected] ' ,
name: 'John Doe'
})
// Usage - multiple
const users = await app . service ( 'users' ). create ([
{ email: '[email protected] ' , name: 'User 1' },
{ email: '[email protected] ' , name: 'User 2' }
])
When creating multiple records, the service must return an array of created records.
update(id, data, params)
Replace a record entirely (PUT semantics):
const usersService = {
async update ( id , data , params ) {
// All fields must be provided
const user = await database . users . replaceOne ( id , data )
if ( ! user ) {
throw new NotFound ( `User ${ id } not found` )
}
return user
}
}
// Usage
const user = await app . service ( 'users' ). update ( 123 , {
email: '[email protected] ' ,
name: 'New Name' ,
// All other fields must be included
})
patch(id, data, params)
Partially update one or multiple records:
const usersService = {
async patch ( id , data , params ) {
// Single record
if ( id !== null ) {
const user = await database . users . updateOne ( id , data )
return user
}
// Multiple records (bulk update)
const users = await database . users . updateMany ( params . query , data )
return users
}
}
// Usage - single
const user = await app . service ( 'users' ). patch ( 123 , {
name: 'Updated Name' // Only update name
})
// Usage - bulk
const users = await app . service ( 'users' ). patch ( null ,
{ verified: true },
{ query: { role: 'user' } }
)
remove(id, params)
Delete one or multiple records:
const usersService = {
async remove ( id , params ) {
// Single record
if ( id !== null ) {
const user = await database . users . deleteOne ( id )
return user
}
// Multiple records (bulk delete)
const users = await database . users . deleteMany ( params . query )
return users
}
}
// Usage - single
const deletedUser = await app . service ( 'users' ). remove ( 123 )
// Usage - bulk
const deletedUsers = await app . service ( 'users' ). remove ( null , {
query: { inactive: true }
})
Params Object
The params parameter provides context for service method calls:
interface Params {
query ?: Query // Query parameters
provider ?: string // 'rest', 'socketio', 'primus', or undefined
route ?: { [ key : string ] : any } // Route parameters
headers ?: { [ key : string ] : any } // HTTP headers
// ... any custom properties
}
Query
Provider
Custom Properties
const result = await service . find ({
query: {
$limit: 25 ,
$skip: 0 ,
status: 'active'
}
})
Custom Methods
Define custom service methods beyond the standard CRUD operations:
const usersService = {
async find ( params ) {
return []
},
async get ( id , params ) {
return { id }
},
// Custom method
async resetPassword ( data , params ) {
const { email } = data
const user = await this . findByEmail ( email )
// Send password reset email
await sendPasswordResetEmail ( user )
return { message: 'Password reset email sent' }
}
}
app . use ( 'users' , usersService , {
methods: [ 'find' , 'get' , 'resetPassword' ]
})
// Usage
await app . service ( 'users' ). resetPassword ({
email: '[email protected] '
})
Custom methods must be explicitly listed in the methods option to be available to external clients.
Protected Methods
Certain method names are reserved and cannot be used as custom methods:
// Reserved names
const protected = [
'all' , 'around' , 'before' , 'after' , 'error' ,
'hooks' , 'setup' , 'teardown' , 'publish' ,
// ... and all EventEmitter methods
]
Lifecycle Hooks
setup(app, path)
Called when the application initializes:
class UserService {
async setup ( app , path ) {
console . log ( `Setting up ${ path } ` )
this . app = app
this . connection = await createDatabaseConnection ()
}
async find ( params ) {
return this . connection . query ( 'SELECT * FROM users' )
}
}
teardown(app, path)
Called when the application shuts down:
class UserService {
async teardown ( app , path ) {
console . log ( `Tearing down ${ path } ` )
await this . connection . close ()
}
}
Service Options
Configure services with options:
interface ServiceOptions {
events ?: string [] // Custom events to emit
methods ?: string [] // Available methods
serviceEvents ?: string [] // Complete event list
routeParams ?: object // Default route params
}
Methods
Events
Route Params
app . use ( 'messages' , messageService , {
methods: [ 'find' , 'get' , 'create' , 'customMethod' ]
})
// Only these methods are available externally
app . use ( 'users' , userService , {
events: [ 'login' , 'logout' , 'passwordReset' ]
})
// Default events (created, updated, patched, removed) + custom events
app . service ( 'users' ). on ( 'login' , ( data ) => {
console . log ( 'User logged in:' , data )
})
app . use ( 'users/:userId/messages' , messageService , {
routeParams: {
userId: 'userId'
}
})
// userId will be available in params.route
Real-World Examples
In-Memory Service
API Wrapper
class MemoryService {
constructor () {
this . store = new Map ()
this . currentId = 0
}
async find ( params ) {
const { query = {} } = params
const { $limit , $skip , ... filters } = query
let items = Array . from ( this . store . values ())
// Apply filters
Object . keys ( filters ). forEach ( key => {
items = items . filter ( item => item [ key ] === filters [ key ])
})
const total = items . length
const skip = parseInt ( $skip ) || 0
const limit = parseInt ( $limit ) || 10
items = items . slice ( skip , skip + limit )
return {
total ,
limit ,
skip ,
data: items
}
}
async get ( id , params ) {
const item = this . store . get ( id )
if ( ! item ) throw new NotFound ( `Item ${ id } not found` )
return item
}
async create ( data , params ) {
const id = ++ this . currentId
const item = { ... data , id }
this . store . set ( id , item )
return item
}
async patch ( id , data , params ) {
const item = await this . get ( id , params )
const updated = { ... item , ... data }
this . store . set ( id , updated )
return updated
}
async remove ( id , params ) {
const item = await this . get ( id , params )
this . store . delete ( id )
return item
}
}
app . use ( 'items' , new MemoryService ())
Best Practices
Validate input - Always validate data in hooks before processing
Handle errors gracefully - Throw appropriate Feathers errors
Return consistent shapes - Keep response formats predictable
Use params for context - Pass authentication, user info via params
Implement only needed methods - Don’t implement methods you don’t use
Document custom methods - Make it clear what custom methods do
Use TypeScript - Type your services for better developer experience
Next Steps
Hooks Learn how to add middleware to service methods
Events Understand real-time events from services