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.Model
is shared across all requests.
If another request comes in and sets a different model before ourafter
hook 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