tap() is a side-effect operator that does not change the emitted value — used for logging and auditing. map() transforms the emitted value — used to wrap responses or rename fields. catchError() catches errors in the stream — used to remap domain exceptions into HTTP exceptions or provide fallback values.
tap(fn) — logging, metrics, auditing. The stream value passes through unchanged.
map(fn) — response wrapping, field renaming, stripping sensitive properties.
catchError(fn) — remapping lower-level errors (TypeORM, Mongoose) into NestJS HTTP exceptions.
Operators execute in the order they are listed in pipe().
catchError must return an Observable — use throwError(() => new Error()) to re-throw.