ryer.io

Extracting Rate Limit Middleware From a 1000-Change Merge

TL;DR

  • Story 2382 lived on an old branch, now 1000+ changes behind because of our React Native JSI migration.
  • 16 merge conflicts, mostly gitignore and yarn.lock, resolved by accepting incoming changes that kept types intact.
  • A hardcoded 404 in app.ts surfaced during what should have been a README change.
  • getDecodedJotToken existed in other services but was missing from this branch — redundancy from divergent merges.
  • Functions that never call a service aren’t service methods; moved them to shared utilities.

Two related pieces of work: getting a stale branch merged, and cleaning up what the merge revealed.

The merge

First, an easy win — a PR for MongoDB documentation updates. Being both reviewer and implementer makes those quick, which is one genuine advantage of working solo.

Then story 2382, blocking malicious users. I assumed I needed a new branch before realising the work existed on an old one, findable under “mitigate attackers”. It was over 1000 changes behind, almost entirely from our React Native migration to the JavaScript Interface.

16 conflicts, mostly gitignore and yarn.lock. I accepted incoming changes wherever they preserved the types and configuration we now depend on.

One oddity: app.ts flagged hardcoded 404 errors during what should have been a trivial README change. The hardcoded status needed refactoring anyway to accommodate the new API and the Auth0 rate limiter, so it turned into real work rather than conflict resolution.

While in there I renamed the rate limiting middleware — it had a verbose name — to rateLimit.MW for consistency with the rest of the codebase. I also hit ambient module declarations in TypeScript colliding with our proxy services’ global declarations, which needed further untangling. Disabling the TS server temporarily while applying the bulk changes made that much less painful.

The missing method

Deeper in, a method called getDecodedJotToken from the auth service was referenced but absent from my branch. Searching the Azure DevOps repository showed it had been part of other merged services and middleware, but had never landed here — exactly the kind of gap iterative divergent merges produce.

Its job is extracting the token from request.headers.authorization. Looking for it turned up near-identical logic already living in extractUserInfoMiddleware and auth0.service.ts. Three copies of the same idea, none of them canonical.

The observation that mattered

Rather than a big refactor, I made a tactical fix: a getTokenFromRequest function in the Auth service.

But looking at the neighbours, getUserFromRequest and getUserFromToken don’t call any external service. They take input and return output. They’d been written as service methods purely by association, and being service methods forced every consumer to depend on the service to reach them.

So they became utility functions in a shared module. That removed the dependency without changing behaviour, and let me use them directly from the rate-limit middleware, which was the actual goal.

The type system pushed back — our JWT payloads don’t carry the custom properties TypeScript expected. @ts-expect-error was the right tool: it marks a known incompatibility explicitly and documents it for whoever reads this next, rather than silently widening a type.

Existing tests validated the changes.

The principle I’d take from this: injecting a dependency to reach a function that doesn’t need it is complexity you’re choosing. If it doesn’t call the service, it isn’t a service method.