Skip to content

feat: add NestJS adapter#55

Open
bogdantarasenko wants to merge 3 commits into
supabase:mainfrom
bogdantarasenko:feat/nestjs-adapter
Open

feat: add NestJS adapter#55
bogdantarasenko wants to merge 3 commits into
supabase:mainfrom
bogdantarasenko:feat/nestjs-adapter

Conversation

@bogdantarasenko
Copy link
Copy Markdown

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() or app.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 full SupabaseContext or a single field, with NestJS pipes applied to the extracted value.
  • Failed auth surfaces as HttpException with { message, code }; the underlying AuthError is exposed on cause so consumers can build their own exception filters.

@nestjs/common is 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 source
  • src/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 @SupabaseCtx with pipes)
  • vitest.config.ts — uses unplugin-swc so the integration tests get emitDecoratorMetadata, which Nest's DI requires (esbuild, vitest's default transform, doesn't emit it)
  • tsconfig.jsonexperimentalDecorators + emitDecoratorMetadata for the same reason
  • tsdown.config.ts, package.json exports, jsr.json — wire the new entry point
  • README.md, src/adapters/README.md, new docs/adapters/nestjs.md — documentation

Test plan

  • pnpm test — unit + integration tests pass on Express and Fastify
  • pnpm buildtsdown emits dist/adapters/nestjs/index.{mjs,cjs,d.mts,d.cts}
  • Reviewer: confirm the optional-peer-dep approach (peerDependenciesMeta) is acceptable so non-Nest users aren't affected
  • Reviewer: sanity-check the vitest.config.ts + unplugin-swc test setup; it's only needed because Nest's DI requires decorator metadata that esbuild doesn't emit

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.
Copy link
Copy Markdown
Collaborator

@mandarini mandarini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread tsconfig.json Outdated
Comment thread vitest.config.ts Outdated
Comment thread vitest.config.ts Outdated
Comment thread src/adapters/nestjs/middleware.ts Outdated
Comment thread src/adapters/nestjs/middleware.ts Outdated
- 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.
@bogdantarasenko bogdantarasenko requested review from a team as code owners May 20, 2026 11:12
@bogdantarasenko
Copy link
Copy Markdown
Author

Thanks for the review @mandarini — all five points addressed in 8552a11.

1. Scoped decorator tsconfigexperimentalDecorators and emitDecoratorMetadata removed from the root tsconfig.json; new src/adapters/nestjs/tsconfig.json extends root and enables them only for the adapter. The root config now excludes src/adapters/nestjs, and pnpm typecheck runs both projects (tsc --noEmit && tsc --noEmit -p src/adapters/nestjs). The scoped include also picks up ../../env.d.ts since the adapter transitively imports resolve-env.ts which uses the Deno global.

2. vitest projects for swcvitest.config.ts now defines a unit project (excludes src/adapters/nestjs/**) and a nestjs project that loads unplugin-swc. Removed the top-of-file comment. Output now reports each project's tests under its label.

3. Non-HTTP contexts now throw — went with option 1 (fail loudly). The guard throws HttpException(500, { code: 'unsupported_context' }) referencing the actual context type, instead of returning true. JSDoc on withSupabase also calls out the HTTP-only constraint prominently at the top. Test updated to assert the throw on both rpc and ws.

4. "Outermost wins" guard escalation fixed — agreed, that was a real escalation hazard given Nest's fixed global → controller → handler order. Removed the if (req.supabaseContext) return true skip; each guard now always re-evaluates against its own configured auth mode, so a handler-level guard can tighten what a global one set. The innermost guard's context wins. The old "skips if context already set" test was replaced with two tests: one proves a stricter handler guard rejects when a permissive outer guard had populated context, the other proves successful inner auth overwrites the preset.

Also cleaned up a stale unused CanActivate import in integration.test.ts while in the area. All 154 tests pass; pnpm typecheck, pnpm lint, and pnpm build are clean.

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.
@bogdantarasenko bogdantarasenko requested a review from mandarini May 20, 2026 11:22
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@supabase/server@55

commit: 36e77b7

Copy link
Copy Markdown
Collaborator

@mandarini mandarini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants