fix(start-server-core): return 404 for API routes without GET handler
Fixes #5473:
API routes without a GET handler returned 200 + SPA shell instead of 404 JSON.
Previously:
- Visiting
/api/xyzwhere only aPOSThandler existed returned 200 OK with the HTML shell.
Now:
- Routes without a matching method (and no
ANYfallback) correctly return 404 JSON ({ "error": "Not Found" }). HEADrequests behave correctly (empty 404 if noGEThandler).
Summary by CodeRabbit
-
New Features
- Improved HTTP method handling: normalized method matching and automatic HEAD→GET mapping when GET exists.
-
Bug Fixes
- Clearer 404 behavior: HEAD returns an empty response when unmapped; other methods return JSON { error: "Not Found" } and stop processing.
-
Tests
- Added unit tests for method resolution, HEAD behavior, ANY-handler fallback, and missing-handler cases.
- Added test helpers and mocks to support routing and start-up scenarios.
-
Chores
- Updated dev build config to alias test helpers for easier testing.
✏️ Tip: You can customize this high-level summary in your review settings.
Walkthrough
Normalize server route handler keys to uppercase, map HEAD to GET when a GET handler exists, consult an ANY fallback, and return early with 404 responses when no handler is found. Adds unit tests, test mocks, and Vite aliases pointing to those mocks for testing.
Changes
| Cohort / File(s) | Summary |
|---|---|
Core handler logic update packages/start-server-core/src/createStartHandler.ts |
Normalize handler keys to uppercase, treat HEAD as GET when GET exists, consult ANY fallback, and return early with 404 (special-case HEAD response body) when no handler is found. |
Unit tests packages/start-server-core/tests/createStartHandler.test.ts |
New tests covering GET/POST/HEAD/ANY resolution and 404 behavior across method/handler configurations and SPA fallback simulation. |
Test mocks packages/start-server-core/tests/mocks/injected-head-scripts.ts, packages/start-server-core/tests/mocks/router-entry.ts, packages/start-server-core/tests/mocks/start-entry.ts, packages/start-server-core/tests/mocks/start-manifest.ts |
New mock modules: empty injectedHeadScripts export, mutable currentHandlers and fake router, startInstance.getOptions() stub, and a minimal start manifest helper. |
Vite test config packages/start-server-core/vite.config.ts |
Add resolve.alias mappings to point virtual/module IDs to the new test mock files (adds import path from 'node:path' and uses path.resolve for aliases). |
Sequence Diagram(s)
sequenceDiagram
participant Client
participant Server as createStartHandler
participant Lookup as Handler Lookup (normalized)
participant HandlerFn as Selected Handler
participant Response
Client->>Server: HTTP Request (method, path)
Server->>Lookup: Normalize handlers keys → UPPERCASE
Server->>Lookup: Lookup by method
alt method is HEAD and GET exists
Lookup-->>Server: select GET handler
else no method match -> try ANY
Lookup-->>Server: select ANY handler (if present)
end
alt Handler found
Server->>HandlerFn: invoke handler
HandlerFn-->>Response: handler response
Response-->>Client: handler-defined status/body
else No handler
alt request method is HEAD
Server->>Response: 404 with empty JSON content-type
else
Server->>Response: 404 with {"error":"Not Found"} JSON body
end
Response-->>Client: 404
end
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
- Areas to review closely:
- Uppercasing/normalization logic vs. existing handler registrations
- HEAD→GET mapping precedence relative to explicit HEAD and
ANY - 404 response shape and content-type differences for HEAD vs other methods
- Vite alias correctness and path.resolve usage
Possibly related PRs
- TanStack/router#5593 — Overlapping edits to
createStartHandler.tsaffecting method-to-handler resolution and fallback behavior. - TanStack/router#5896 — Related changes touching server route handler selection and dispatch flow in
createStartHandler.ts.
Suggested reviewers
- schiller-manuel
Poem
🐇
I hopped through keys and made them bright,
HEAD follows GET when it’s in sight,
ANY listens when others flee,
404s now honest, plain to see,
Mocks snug in burrows — tests sleep tight.
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title clearly and specifically describes the main fix: returning 404 status for API routes without GET handlers, which directly addresses issue #5473. |
| Linked Issues check | ✅ Passed | The PR successfully addresses all coding requirements from issue #5473: returns proper 404 JSON for missing HTTP methods, handles HEAD requests correctly, and prevents incorrect 200 responses with HTML. |
| Out of Scope Changes check | ✅ Passed | All changes are directly scoped to fixing the issue: the core handler logic, comprehensive test coverage, Vite config aliases for test mocks, and necessary test mock files are all aligned with addressing the 404 handling requirements. |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
View your CI Pipeline Execution ↗ for commit e3ccdddf24fdff44c2d764fe68953490b282a664
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:eslint,test:unit,tes... |
⛔ Cancelled | 14s | View ↗ |
nx run-many --target=build --exclude=examples/*... |
✅ Succeeded | 23s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-11-19 20:49:10 UTC