feat: add NestJS adapter#55
Conversation
Ships `@supabase/server/adapters/nestjs`:
- `withSupabase(opts)` — class guard for `@UseGuards()` and
`useGlobalGuards()`, supporting Express and Fastify
- `@SupabaseCtx(key?, ...pipes)` — param decorator returning the full
SupabaseContext or a single field, with NestJS pipes applied to the
extracted value
- 401s thrown as `HttpException` with `{ message, code }`; the
underlying `AuthError` is exposed on `cause`
Adds `@nestjs/common` as an optional peer dep (`^10 || ^11`), wires the
new export in package.json / jsr.json / tsdown.config.ts, and enables
`experimentalDecorators` + `emitDecoratorMetadata` in tsconfig. Test
setup uses unplugin-swc via vitest.config.ts so integration tests can
boot a real Nest app on both Express and Fastify.
Docs: README quickstart + docs/adapters/nestjs.md.
mandarini
left a comment
There was a problem hiding this comment.
Hi @bogdantarasenko ! Thank you very much for this PR and for contributing to Supabase! :D I left some change requests, can you please take a look?
- Scope `experimentalDecorators` + `emitDecoratorMetadata` to `src/adapters/nestjs/tsconfig.json` (extends root) and exclude the adapter from the root project so the options aren't enforced repo-wide. `typecheck` now runs both projects. - Convert `vitest.config.ts` to the `projects` syntax so the `unplugin-swc` transform applies only to the nestjs project; the unit project runs unchanged with esbuild. - Throw `HttpException` (500, `unsupported_context`) instead of returning true on non-HTTP execution contexts so misuse fails loudly on the first request rather than silently no-op'ing on every RPC/WebSocket message. - Remove the "skip if context already set" branch so handler-level guards can tighten what a global guard set. Previously the outer (global) guard always won under Nest's global → controller → handler order, so a stricter handler-level guard could be silently bypassed. Tests updated; `@SupabaseCtx` decorator unchanged. - Drop unused `CanActivate` import from integration.test.ts.
|
Thanks for the review @mandarini — all five points addressed in 8552a11. 1. Scoped decorator tsconfig — 2. vitest 3. Non-HTTP contexts now throw — went with option 1 (fail loudly). The guard throws 4. "Outermost wins" guard escalation fixed — agreed, that was a real escalation hazard given Nest's fixed global → controller → handler order. Removed the Also cleaned up a stale unused |
Resolves conflicts with the Elysia adapter (supabase#46) and the 1.1.0 release that landed on main while review feedback was being addressed. - README.md / src/adapters/README.md: list both Elysia and NestJS in the adapter tables; preserve the community-driven note added upstream. - package.json / jsr.json / tsdown.config.ts: combine Elysia + NestJS entries, peer deps, and bundle externals. Keep upstream's `@supabase/supabase-js` ^2.105.4 bump. - pnpm-workspace.yaml: set `allowBuilds` policy for `@nestjs/core` and `@swc/core` to `false` — they're dev-only deps with no native bindings we need to compile. - pnpm-lock.yaml: regenerated.
commit: |
mandarini
left a comment
There was a problem hiding this comment.
Hi @bogdantarasenko, thank you so much for the thorough follow-up on this! You addressed every point really carefully, and the two new tests around guard re-evaluation (inner-rejects + inner-overwrites) make the intended semantics very clear. The throw-on-non-HTTP fix is exactly what I was hoping for, and the scoped tsconfig.json + vitest projects split is clean.
One last thing before we can merge: docs/adapters/nestjs.md still has the old "Skip behavior" section (lines 118-122) describing the skip-if-context-already-set behavior, which no longer matches the code after this round. The JSDoc on withSupabase already has the correct wording ("Always runs, even if a previous guard already set the context..."); could you rewrite that doc section to match? The closing advice about preferring per-route @UseGuards(...) without a global guard is still good and worth keeping.
Thanks again for sticking with this through two rounds of review, it is contributions like yours that make Supabase what it is.
Summary
Adds a community NestJS adapter under
@supabase/server/adapters/nestjs, alongside the existing Hono and H3 adapters.withSupabase(opts)returns a class guard usable with@UseGuards()orapp.useGlobalGuards(new (withSupabase(...))()). Works on both Express and Fastify platforms; HTTP/2 pseudo-headers and non-HTTP execution contexts (rpc/ws) are handled.@SupabaseCtx(key?, ...pipes)is a param decorator that returns the fullSupabaseContextor a single field, with NestJS pipes applied to the extracted value.HttpExceptionwith{ message, code }; the underlyingAuthErroris exposed oncauseso consumers can build their own exception filters.@nestjs/commonis added as an optional peer dep (^10.0.0 || ^11.0.0), so users who don't import the adapter aren't forced to install Nest.What's in the diff
src/adapters/nestjs/{middleware,decorator,index}.ts— adapter sourcesrc/adapters/nestjs/{middleware,integration}.test.ts— unit tests for the guard/decorator plus integration tests that boot a real Nest app on Express and Fastify (covers@UseGuards,useGlobalGuards, and@SupabaseCtxwith pipes)vitest.config.ts— usesunplugin-swcso the integration tests getemitDecoratorMetadata, which Nest's DI requires (esbuild, vitest's default transform, doesn't emit it)tsconfig.json—experimentalDecorators+emitDecoratorMetadatafor the same reasontsdown.config.ts,package.jsonexports,jsr.json— wire the new entry pointREADME.md,src/adapters/README.md, newdocs/adapters/nestjs.md— documentationTest plan
pnpm test— unit + integration tests pass on Express and Fastifypnpm build—tsdownemitsdist/adapters/nestjs/index.{mjs,cjs,d.mts,d.cts}peerDependenciesMeta) is acceptable so non-Nest users aren't affectedvitest.config.ts+unplugin-swctest setup; it's only needed because Nest's DI requires decorator metadata that esbuild doesn't emit