I recently uncovered one of the most elusive bugs I've ever dealt with β and it had been quietly sitting in my codebase for over 3 years.
Out of nowhere, MongoDB logs were showing slow queries against the wrong collections:
ns: swimdb.strokes2324
query: { ageGroup: "13-14", ... }
But that didnβt make sense β this should have used the best_strokes2324 collection.
My service uses a dynamic setModel() method to switch MongoDB collections based on query parameters. It looked fine:
setModel = (collectionName) => {
this.app.get("mongoClient").then((db) => {
this.Model = db.collection(collectionName);
});
};
And in the hook:
await context.service.setModel("best_strokes2324");
Looks safe, right? π
The setModel() function wasnβt returning anything β meaning that the await did absolutely nothing.
Requests were racing ahead before the model was actually updated. As a result:
Some queries hit the right collection
Others hit a stale or incorrect one
And the worst part: it all looked fine most of the time
One word: async.
setModel = async (collectionName) => {
const db = await this.app.get("mongoClient");
this.Model = db.collection(collectionName);
};
Or if you still use .then():
setModel = (collectionName) =>
this.app.get("mongoClient").then((db) => {
this.Model = db.collection(collectionName);
});
Now await setModel(...) actually waits.
A missing return inside an async-like method can silently break everything.
If your service state depends on timing, you must understand whether the mutation is blocking.
MongoDB slow query logs are gold β they helped me trace the issue when nothing else would.
setModel() in the after HookYou might wonder β if we already called await setModel() in the before hook, why call it again in the after hook?
The answer is subtle but critical:
context.service.Modelis shared across all requests.
If another request comes in and sets a different model before ourafterhook runs, we could end up running queries against the wrong collection β even if we set it earlier.
Feathers services are singletons. The model (this.Model) isn't scoped per request β it's global. But context.params is request-specific. So we store season in context.params._season during the before hook and re-call setModel() in the after hook, just before any late queries like .distinct().
That one extra line protects us from race conditions:
await context.service.setModel("collection_prefix" + context.params._season);
This ensures the model is always correct at the time itβs needed β even if another user is hammering the server with overlapping requests.
π Result
After the fix:
No more inconsistent collections
Query times dropped dramatically
Confidence restored