Using FeathersJS Service Hooks to Add Request Logging and Validation
Learn how to use FeathersJS service hooks to centralize request logging and validation without bloating your service methods.
22 May 2026, 00:41 UTC

Problem: Cross‑cutting concerns clutter service code
When building a REST API with FeathersJS you often need to log incoming requests, validate payloads, or enrich data before it hits the database. If you put that logic directly inside each service method you end up with repetitive code that is hard to maintain.
Thesis: Hooks keep concerns separate and composable
FeathersJS provides a hook system that lets you register functions to run before or after service methods such as find, get, create, update, patch and remove. Each hook receives a context object containing params, data and the service instance, and can modify the context or return a promise. By chaining hooks you can build reusable pipelines for logging, validation, authentication, etc.
How the hook system works
Hooks are plain JavaScript functions that are called in the order they are registered. If a hook returns a promise (or uses async/await) the next hook waits for it to resolve. Forgetting to await an asynchronous operation can cause later hooks to see stale data, which is a common source of race conditions.
Creating a logging hook
Use the Feathers CLI to generate a hook skeleton:
# Run from the project root
feathers generate hook
# Choose "before" hook, give it a name like request-logger, and select the service(s) you want to target.
The generated file (src/hooks/request-logger.js) looks like this:
// src/hooks/request-logger.js
module.exports = function (options = {}) {
return async function (context) {
// context.method is the service method (create, find, etc.)
// context.params contains query, user, etc.
// context.data holds the payload for create/update/patch
console.log(`[${new Date().toISOString()}] ${context.method} called on ${context.path}`);
console.log('Payload:', context.data);
// Return the (potentially modified) context for the next hook
return context;
};
};
Adding a validation hook
Similarly generate a validation hook:
feathers generate hook
# Choose "before" hook, name it payload-validator, attach to the same service.
Implement a simple schema check using a library like Joi (you would install it separately):
// src/hooks/payload-validator.js
const Joi = require('joi');
module.exports = function (options = {}) {
const schema = Joi.object({
title: Joi.string().min(3).max(100).required(),
completed: Joi.boolean()
});
return async function (context) {
const { error } = schema.validate(context.data, { abortEarly: false });
if (error) {
// Throw a Feathers error to halt the pipeline
const message = error.details.map(d => d.message).join(', ');
throw new Error(`Validation failed: ${message}`);
}
return context;
};
};
Worked example: wiring hooks to a service
- Generate a service (if you don’t have one already):
feathers generate service– choose a MongoDB or NeDB store, name ittodos. - Open the generated service file
src/services/todos/todos.service.js. - Import the hooks and add them to the appropriate method hooks:
// src/services/todos/todos.service.js
const { authenticate } = require('@feathersjs/authentication').hooks;
const requestLogger = require('../../hooks/request-logger');
const payloadValidator = require('../../hooks/payload-validator');
module.exports = {
before: {
all: [ authenticate() ],
find: [],
get: [],
create: [ requestLogger(), payloadValidator() ],
update: [ requestLogger(), payloadValidator() ],
patch: [ requestLogger(), payloadValidator() ],
remove: []
},
after: {
// ...
},
error: {}
};
Start the application with npm start. Then send a request using curl:
curl -X POST http://localhost:3030/todos -H "Content-Type: application/json" -d '{"title":"Learn FeathersJS","completed":false}'
You should see two lines in the console from the request‑logger hook (timestamp, method, path, payload) before the service’s default create logic runs. If you omit the title field, the validation hook throws and the client receives a 400 error with the validation message.
Trade‑offs and limitations
- Debugging visibility: Because logic lives in separate files, tracing a request’s flow requires checking each hook in order. Keeping hooks small and well‑named helps.
- Async pitfalls: Forgetting to
awaitinside anasynchook can cause race conditions where later hooks see stale data, which is a common source of race conditions. - Over‑use: Adding dozens of hooks for minor tasks can make the service harder to understand. Reserve hooks for cross‑cutting concerns that truly benefit from centralization.
Actionable closing
To start using hooks in your own FeathersJS project:
- Run
feathers generate hookfor each concern you want to extract (logging, validation, enrichment). - Implement the hook as an
asyncfunction that returns the context (or throws on error). - Import the hooks in the service file and add them to the
before,afterorerrorarrays for the methods you need. - Start the server, exercise the API with curl or Postman, and verify that the hook’s console output appears as expected.
- Periodically review the hook chain; if a hook becomes too complex, consider moving logic into a service method or a reusable utility.
By keeping each hook focused and awaiting any asynchronous work, you gain a clean, composable way to extend service behavior without sacrificing readability.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.