Custom methods are one of the most commonly requested features for Feathers. As the FAQ points out, it is possible to handle 90% of all use cases with the normal CRUD service interface and hooks for workflows. It does however still leave some edge cases that would be good if a service could expose additional methods externally. One reason for this limitation was that it is difficult to secure arbitrary methods properly. The new https://github.com/feathersjs/hooks general purpose hooks make this much easier.
This is why starting at v5, in addition to the existing service methods, it will be possible to register your own custom methods. A custom method has a name and __fixed__ parameters of (data, params) where data is the payload and params is the usual service method parameters (including things like provider, authenticated user, query etc.). Method information can be passed as options to app.use (app.use(path, service, options)). This will also allow to more easily disable existing service methods (or their events) externally.
class MyMessageService {
async create (data, params) {}
async findOne (data, params) {}
async resendNotification (data, params) {}
}
app.use('/messages', new MyMessageService(), {
methods: [ 'create', 'findOne', 'resendNotification' ],
events: [ 'my-custom-event' ]
})
The available options are:
methods: A list of all publicly exposed methods. Will default to the standard service methods. When passed those will have to be added explicitly.events: The custom service events (previous in service.events)X-Service-Method header. data will be the body payload.socket.emit(methodName, 'messages', { username: 'dave' }, query)I think that this is a great addition that will allow more flexibility for building APIs and help keeping the hooks lean and simple.
hello .
thanks to public this option.
where route findOne in rest api ?
/message/:id ?
I think that a custom method must also be able to have as arguments id, data, params or id, params.
To determine if there is id or not, we could apply this mechanism also on /messages/:id.
There is currently a way to expose custom methods to the desired routes with service.methods & httpMethod: https://github.com/feathersjs/feathers/pull/924 https://github.com/feathersjs/feathers/pull/945
The downside is for client-side use, the routes are just not predefined.
const { rest } = require('@feathersjs/express')
class MyMessageService {
methods = {
findOne: ['data', 'params'],
resendNotification: ['data', 'params']
}
async create (data, params) {}
@rest.httpMethod('POST', ':__feathersId/find-one')
async findOne (data, params) {}
@rest.httpMethod('POST', ':__feathersId/resend-notification')
async resendNotification (data, params) {}
}
app.use('/messages', new MyMessageService())
We could do without service.methods and trust the parameters named by @feathersjs/hooks but it is no longer possible to know the name of the arguments before executing the function since https://github.com/feathersjs/hooks/pull/37.
Agreed, supporting an id does make sense. I'd however like to avoid any route mapping that only works for a specific transport. The HTTP specification is also fairly clear on that a URI should only specify the _resource location_ (service + resource id) and not contain any actions (verbs).
I also prefer keeping the service options as a separate object.
@daffl I agree, the only thing that bothers me is the constraint of fixed parameters.
To determine if there is id or not, we could apply this mechanism also on /messages/:id.
To determine if there is data or not, we could use the verb GET rather than POST.
Params is always there as the last argument.
I didn't understand what I was saying, surely fatigue. 馃槄
When the client makes his request, we do not have the url or the arguments that the function expects.
All we know is that this is not a basic method and that there are X arguments.
In my opinion, it's easier if the Feathers client always does a POST, and gives the array of arguments in body. Whether it's [id, data, query], [id, query], [data, query] or just [query].
It is only once the server receives the name of the method via the header and the array of arguments that it can look at the service and determine what each argument corresponds to in the array thanks to the signature of the function.
It will therefore be necessary to do a qs.parse(qs.stringify()) on the query on the server side to keep consistency with the other methods. The id usually coming from the url, it may be necessary to do the same thing.
Calling via transports
- Via REST a custom method can be called using the POST method and the
X-Method-Overrideheader.datawill be the body payload.
I'm assuming the feathers rest client would handle this automatically?
Sounds very nice! I've had either methods outside the normal CRUD or wanted an additional custom function and it seemed appropriate to include it inside the same service.
Hi, do you have to declare the default methods (create, patch, update, remove) as well when initializing the service? or just need to declare custom methods?
Currently all methods need to be defined. That way it is possible to completely disable a default method for external access (instead of having to rely on a hook that still runs the method).
This way is to completely disable the method (e.g patch), but if I want to disable for certain transport (e.g REST), I would still need to register the method, and disable via hooks right?
@bwgjoseph I don't think I had a case where I wanted to disable for one transport but not another but yes, that would be correct.
@bertho-zero is what you described in your comment above (https://github.com/feathersjs/feathers/issues/1976#issuecomment-638219533) documented anywhere besides that comment? The use of rest.httpMethod decorator I mean.
I stumbled upon the Symbol and httpMethod method declarations while reading the code, googled it and that's how I got here. But this issue and those two pull requests you mentioned in your comment are the only things that show up when I search "httpMethod" within this repository or on the FeathersJS documentation site.
Relevant section of the FAQ page in the docs still suggests we define a separate service if we want a custom method at a custom path or do this using the hooks somehow as described in the answer for findOrCreate.
Just trying to pick the framework for the first time and migrate exiting API to it. Ability to define custom handlers was a corner stone for me and I would assume I'm not the only one who faced this. Would be nice if it was easier to discover
That decorator will no longer be available in v5 and later (but a compatibility wrapper is available mentioned in https://github.com/feathersjs/feathers/pull/2270#issuecomment-810786581).
In general, Feathers aims at letting you implement your API _without_ having to worry about the transport (and especially HTTP) specifics, instead providing set of best practises and design patterns that work for any transport protocol. The API outlined here, in https://github.com/feathersjs/feathers/pull/2270 and in the prerelease documentation is the recommended way going forward for custom functionality and works for both, HTTP and websockets.
Oh, that's good to know. Thank you so much for the heads up!
Most helpful comment
Agreed, supporting an
iddoes make sense. I'd however like to avoid any route mapping that only works for a specific transport. The HTTP specification is also fairly clear on that a URI should only specify the _resource location_ (service + resource id) and not contain any actions (verbs).