Skip to content

Conversation

@marshallswain
Copy link
Member

@marshallswain marshallswain commented Jan 10, 2026

Summary

This PR implements the feature requested in #3457, allowing hooks to run on all service methods while providing fine-grained control over which methods are exposed externally.

Changes

@feathersjs/feathers

Type Declarations (declarations.ts)

  • Updated methods description to clarify it controls which methods all hooks apply to
  • Added new externalMethods option to control HTTP transport exposure

Service (service.ts)

  • Added getExternalMethods() function (defaults to methods for backwards compatibility)
  • Updated normalizeServiceOptions() to include externalMethods

Hooks (hooks.ts)

  • Added allMethods to hook store to track which methods receive all hooks
  • Modified collectHooks() to only include all hooks for methods in allMethods
  • Updated hookMixin() to allow lazy creation of hook managers for any service method

HTTP Transport (http/index.ts)

  • Updated to use getExternalMethods() for HTTP access control

Tests

  • Added tests for lazy hook registration on unconfigured methods
  • Added tests for all hooks scoping
  • Added tests for externalMethods HTTP behavior

Features

1. Individual method hooks work without configuration

// Can register hooks on ANY method, even if not in `methods`
service.hooks({
  helperMethod: [myHook]  // works!
})

methods controls which methods receive all hooks

app.use('myService', new MyService(), {
  methods: ['find', 'get', 'create']
})

service.hooks({
  around: {
    all: [loggingHook]  // only runs on find, get, create
  }
})

3. externalMethods controls HTTP exposure

app.use('myService', new MyService(), {
  methods: ['find', 'get', 'create', 'internalAction'],  // all get hooks
  externalMethods: ['find', 'get', 'create']  // only these exposed via HTTP
})

Backwards Compatibility

  • If externalMethods is not specified, it defaults to methods
  • Existing code works without modification
  • All 354 existing tests pass

Related

- Individual method hook chains now work without prior configuration
- Add externalMethods option to control HTTP/transport exposure
- methods option now controls which methods receive 'all' hooks
- Backwards compatible: externalMethods defaults to methods
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 10, 2026

Deploying feathers-eagle with  Cloudflare Pages  Cloudflare Pages

Latest commit: 09bb238
Status: ✅  Deploy successful!
Preview URL: https://2c692814.feathers-a8l.pages.dev
Branch Preview URL: https://v6-hooks-all-methods.feathers-a8l.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 10, 2026

Deploying feathers-dove with  Cloudflare Pages  Cloudflare Pages

Latest commit: 09bb238
Status: ✅  Deploy successful!
Preview URL: https://09e7e607.feathers.pages.dev
Branch Preview URL: https://v6-hooks-all-methods.feathers.pages.dev

View logs

@daffl
Copy link
Member

daffl commented Jan 10, 2026

I'm wondering if we need to add a new API if we are promoting using decorators going forward. For internal methods you need to list the parameter names of the method anyway to make it useful (to have things like context.message).

This already works and lets you register hooks on any method:

import { hooks } from 'feathers'

class MyService {
  @hooks([]).params('message')
  internalMethod(message: string) {}
  
  @hooks([])
  find() {
  }
}

@marshallswain
Copy link
Member Author

There are still use cases where decorators will not work well, like when using built-in services from a database manager instance. It's a bit verbose to do custom adapter classes just to be able to add hooks.

@marshallswain
Copy link
Member Author

I forgot about the .params() calls

@daffl
Copy link
Member

daffl commented Jan 11, 2026

Wouldn't that work with the object wrapper?

import { hooks } from 'feathers'
import { MemoryService } from 'feathers-memory'

const messageService = hooks(new MemoryService(), {
  myMethod: middleware([]).params('message')
})

app.use('messages', messageService)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants