Day#1: Building Clean, Breaking Things: The Unexpected Cost of a Global Exception Filter

The goal seemed trivial: create a global ExceptionFilter to unify error responses across the backend. A quick win. A small boost in DX. What followed instead was a full day of debugging TypeScript build issues, resolving unexpected behavior in ESM mode, and untangling the consequences of mixing modern monorepo tooling with legacy compiler features.

This post is a breakdown of what went wrong β€” and how it was ultimately fixed.


The setup

The project stack:

The intention was simple: place the reusable filter in libs/filters, export it via @tin-filters/all-exceptions.filter, and import it globally in the app. Nothing revolutionary.

Until everything broke.


What went wrong

The moment the filter was imported using a path alias (@tin-filters/...), everything started to fail in different places depending on the tool used.

All because of one decision: combining TypeScript path aliases with project references and ESM.


What was fixed

Over the course of a few iterations, the following simplifications were made:


Key takeaway

If you're not using tsc --build, don't use composite: true.
If you want path aliases to β€œjust work”, keep them purely TypeScript-side β€” not at runtime.
Simpler is safer.


What’s next

The filter works. The monorepo builds. The aliases resolve.
Next step? Back to building the actual system β€” now with one fewer architectural landmine in place.