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:
- NestJS with TypeScript in ESM mode
- Turborepo for managing multiple applications and shared libraries
- Path aliases declared in
tsconfig.base.json - Separate shared library for infrastructure concerns (
libs/filters) - Testing with Jest, type checking with tsc, running via ts-node
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.
tsc --buildthrewTS6059andTS6307— complaints about files being outsiderootDirand not part of the build.ts-nodein ESM mode crashed withERR_MODULE_NOT_FOUND— ignoringtsconfig.pathsentirely.- Jest threw
ts-jestwarnings and runtime errors — trying to transform.jsfiles withoutallowJs.
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:
- Removed
composite: trueand project references — not needed unless usingtsc --build - Kept global aliases in
tsconfig.base.json(e.g.,@tin-filters/*) - Explicitly included shared libraries in each
tsconfig.jsonthat needs them (include: ["src", "../../../libs/filters"]) - Avoided using aliases at runtime in
ts-node— replaced with relative imports (../../libs/filters/...) to avoid ESM issues - Cleaned
.jsartifacts fromsrc/to preventts-jestwarnings - Restored
commitlinthook usinghusky installand verified permissions
Key takeaway
If you’re not using
tsc --build, don’t usecomposite: 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.

Dodaj komentarz