ErrLookupBackground articles › BadRequestException (HTTP 400) — NestJS 'Bad Request' Errors: Why They Fire and How to Fix Them

BadRequestException (HTTP 400) — NestJS 'Bad Request' Errors: Why They Fire and How to Fix Them

BadRequestException is the NestJS exception class that maps to HTTP 400 Bad Request, thrown when an incoming request is malformed, references a missing resource, violates a domain rule, or trips a security gate. Across cal.com, Hoppscotch, and Immich it is the most common client-side rejection a developer meets on POST/GET/PATCH calls — for input-validation failures, credential lookups, OAuth flow checks, and catch-all wrappers that re-throw upstream errors as a generic 400. Because the class carries only a free-text message, the same 400 status can mean anything from 'you sent a bad field' to 'the server swallowed an internal failure', so reading the message string and the surrounding logs is essential.

Distilled from 243 documented records across 4 repositories.

Background

BadRequestException is a built-in NestJS exception that the framework's global exception filter serializes into an HTTP 400 response body. All four repositories in this family (calcom/cal.diy, hoppscotch/hoppscotch, immich-app/immich) are NestJS applications, so the class is imported from @nestjs/common and constructed as new BadRequestException(message), optionally with a cause. From the caller's side it looks identical regardless of trigger: a JSON error whose message field is whatever string the throw site supplied. That uniformity is both the strength and the weakness of the family — the status is stable and cacheable, but the message is the only signal a client has about what went wrong, and several records show the message being misleading or empty of root cause.

The family exists because HTTP 400 is the spec-correct way to tell a client 'fix your request and try again'. The honest uses are input validation: missing email or code (record 1), a malformed ISO-8601 date (record 2), an inverted or oversized time window (records 11 and 24), a job name outside the ManualJobName enum (record 14), or a slotDuration not in the event type's allowed array (record 25). In these cases the 400 is genuinely the client's bug and the message names the offending field. The same class is also used as a domain-rule gate: a unified-calendar action limited to Google Calendar (record 15), a check that prevents a team from accumulating duplicate Google Meet credentials (record 17), a guard that blocks booking-redirect cycles (record 26), and an API-key permission-escalation check that refuses to mint a broader-scope key from a limited one (record 22). These are policy rejections, not malformed input, but the framework still reports them as 400.

A second, more error-prone pattern uses BadRequestException as a catch-all that swallows an upstream failure and re-throws a friendlier message. IcsFeedService.save (record 3) logs the real exception and throws 'Could not add ICS feeds' without chaining the cause; AppleCalendarService.saveCalendarCredentials (record 18) stringifies the inner error into the message, discarding stack and cause; the SMTP verifier (record 20) wraps with the original as cause; PrivateLinksService.createPrivateLink (record 5) forwards the inner Error.message verbatim, which can leak a Prisma constraint text. The most extreme variant is in cal.com's VerificationAtomService (records 0, 1, 6): the catch predicates compare upstream Error messages against the literal strings 'invalid_code' and 'BAD_REQUEST', but the upstream actually throws 'Invalid verification code' and 'Email and code are required', so both branches are dead and every failure collapses into the generic 'Verification failed'. The 400 status is therefore not always a reliable signal of what the client did wrong.

Finally, several records deliberately report a not-found condition as 400 rather than 404. The asset-metadata endpoint (record 13) throws 400 when a key was never written; the Office 365 (record 19) and Apple Calendar (record 21) connectivity checks throw 400 when no credential row exists; the OAuth /authorize endpoint (record 10) throws 400 for a missing client 'so callers cannot distinguish missing client from bad client id'. This is a library-specific convention, not universal: the records themselves flag the semantic mismatch ('a not found condition is reported as 400 rather than 404'), so a client should not assume a 400 always means retryable bad input.

Common causes

What usually fixes it

Go deeper

Documented occurrences

…and 223 more across the corpus — use search.

Honest provenance: generated on 2026-08-12 from AI-assisted analysis of the linked records. See how records are made.