[nitro 2/6] Bridge the Express app into Nitro; single-origin dev - #4702
Draft
cprecioso wants to merge 4 commits into
Draft
[nitro 2/6] Bridge the Express app into Nitro; single-origin dev#4702cprecioso wants to merge 4 commits into
cprecioso wants to merge 4 commits into
Conversation
All HTTP API traffic (operations, auth, CRUD, custom APIs) now flows
same-origin through Nitro's serverEntry into the generated Express app
via fromNodeHandler; page requests fall through to the renderer.
- new server templates: nitro/{expressBridge,serverEntry,apiManifest,
setup}.ts; new NitroRoutesG.hs computes bridged path prefixes (widened
to a catch-all when a setupFn registers unenumerable routes)
- serverEntry short-circuits ws upgrades and prerender requests, and
strips Express-added headers on fallthrough (helmet CSP must not leak
onto renderer HTML)
- terminal JSON 404s inside /auth, /operations, /crud (a typo'd
operation must never fall through to the SPA shell); wrong-port page
replaced by GET /_wasp/health
- status-preserving branch in the Express error handler (h3's next(err)
otherwise collapses 4xx-carrying errors like body-parser's 413 to 500)
- setupFn runs against the bridged app too (server: throwing Proxy with
a migration message; standalone dev process still runs it unchanged)
- single-origin defaults: REACT_APP_API_URL '' (same-origin),
WASP_SERVER_URL dev default :3000; server .env loaded set-if-undefined
into the Vite process in dev; ws client keeps an explicit :3001
fallback until the crossws phase
- virtual user modules plugin now maps server-side modules too; dedupe
by module id (fixes duplicate object keys when two operations share a
file, also latent in the server-side .js plugin)
- nitro declared in the generated server's deps (its tsc compiles
src/nitro/*.ts)
The standalone dev server keeps running on 3001 (jobs, socket.io,
setupFn-with-server) but its HTTP routes go unused.
An app whose setupFn awaits a job submit (kitchen-sink does) hung every dev request: nothing starts pg-boss in the Nitro worker, so pgBossStarted never resolves and the setup gate never opens. ensurePgBossStarted() starts pg-boss on first submission when nothing else has. No Wasp jobs execute in that instance (registerJob only runs via the standalone process's allJobs import), and the started promise is cached on globalThis so dev reloads don't stack instances (verified: stable pg_stat_activity across edits, exactly-once job execution in the standalone process). Transitional until job execution moves into the Nitro worker. Also: kitchen-sink's OAuth-link e2e assertion now matches the relative same-origin href.
cprecioso
had a problem deploying
to
railway-deploy-test
August 12, 2026 18:31 — with
GitHub Actions
Failure
cprecioso
had a problem deploying
to
fly-deploy-test
August 12, 2026 18:31 — with
GitHub Actions
Failure
@wasp.sh/spec
@wasp.sh/wasp-cli
@wasp.sh/wasp-cli-darwin-arm64-unknown
@wasp.sh/wasp-cli-darwin-x64-unknown
@wasp.sh/wasp-cli-linux-arm64-glibc
@wasp.sh/wasp-cli-linux-x64-glibc
@wasp.sh/wasp-cli-linux-x64-musl
commit: |
15 tasks
Contributor
|
This pull request is now stale. Next steps if the PR is still relevant, and you are able to devote time to it:
If no action is taken, this PR will be automatically closed in 7 days. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Part 2 of the Nitro migration stack (stacked on #4701). This is THE dev cutover: all HTTP API traffic goes same-origin through Nitro on port 3000, bridged into the (unchanged) generated Express app.
fromNodeHandler(app)fromnitro/h3, reached through a generated Nitro serverEntry (h3defineHandler). Express's routing stays the single source of truth for API paths — the serverEntry only does a codegen'd prefix check (fast-path), short-circuits ws upgrades and prerender requests, and strips Express-added headers when Express falls through (helmet's API CSP must not land on renderer HTML). Unmatched routes fall through to the renderer natively via Express's out-next()./auth,/operations,/crudget terminal JSON 404s (a typo'd operation must never come back as a 200 SPA shell). The dev wrong-port page dies;GET /_wasp/healthreplaces the/health response (/is a page now).next(err)discardserr.status/err.statusCode(body-parser 413s,http-errors-style middleware would all become 500s) — a status-preserving branch in the Express error handler keeps them.HttpErrorhandling is byte-identical.serverbecomes a Proxy that throws a descriptive migration error when touched (the rawhttp.Serverno longer exists on this path). When a setupFn exists, the bridged prefix list widens to a catch-all so setupFn-registered routes keep working. The standalone dev process (jobs, socket.io, setupFn-with-server) keeps running on 3001 unchanged; its HTTP routes go unused — transitional until parts 3/4.REACT_APP_API_URLdev default becomes same-origin ("", baseDir-aware),WASP_SERVER_URLdev default moves tohttp://localhost:3000; the server's dev.envis loaded (set-if-undefined, matching-r dotenv/configsemantics) into the single Vite process. The socket.io client keeps an explicit:3001dev fallback until part 3 (crossws).wasp/server), which surfaced a real dedupe bug — two operations declared in the same file generated duplicate object keys. Fixed by deduping on the virtual module id, in the server-side.jsplugin too (where it was latently shipping duplicates).nitrois declared in the generated server's deps (itstsc --buildcompilessrc/nitro/*.ts).Verified on a live app (username auth, parameterized custom
api,apiNamespace, throwing api, prerender route, setupFn, websockets,PORT=3001planted in.env.server): operations + auth signup/login/me through:3000in dev and built output, JSON 404 tiers, SPA-shell fallthrough with zero leaked Express headers, 422/413 statuses, health endpoint, hydration + prerender intact, standalone server + socket.io still functional on 3001, dev port unhijacked. 657/657 unit tests; e2e goldens regenerated and re-verified in comparison mode (947/947 twice).Bonus finding: the node-server preset traces Prisma (including the native engine) into the output with zero
externalsconfig — that was the last open platform-risk question from the spikes.Type of change
Checklist
I tested my change in a Wasp app to verify that it works as intended.
🧪 Tests and apps:
examples/kitchen-sink/e2e-tests.waspc/data/Cli/templates, as needed.examples/, as needed.examples/tutorials) I updated the tutorial in the docs (and vice versa).📜 Documentation:
web/docs/.🆕 Changelog: (if change is more than just code/docs improvement)
waspc/ChangeLog.mdwith a user-friendly description of the change.web/docs/migration-guides/.versioninwaspc/waspc.cabalto reflect the changes I introduced.