@supabase/server 1.1.0-rc.66 → 1.2.0-rc.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -266,11 +266,12 @@ Adapters wrap `withSupabase` for a specific framework's middleware contract. The
266
266
 
267
267
  > **Adapters are a community-driven initiative.** They're developed, maintained, and evolved by contributors — including responding to upstream framework changes. See [`src/adapters/README.md`](src/adapters/README.md) for the contribution requirements (tests, types, docs, build wiring) if you'd like to add or help maintain one.
268
268
 
269
- | Framework | Import | Framework version | Docs |
270
- | --------- | ---------------------------------- | ----------------- | -------------------------------------------------- |
271
- | Hono | `@supabase/server/adapters/hono` | `^4.0.0` | [docs/adapters/hono.md](docs/adapters/hono.md) |
272
- | H3 / Nuxt | `@supabase/server/adapters/h3` | `^2.0.0` | [docs/adapters/h3.md](docs/adapters/h3.md) |
273
- | Elysia | `@supabase/server/adapters/elysia` | `^1.4.0` | [docs/adapters/elysia.md](docs/adapters/elysia.md) |
269
+ | Framework | Import | Framework version | Docs |
270
+ | --------- | ---------------------------------- | ---------------------- | -------------------------------------------------- |
271
+ | Hono | `@supabase/server/adapters/hono` | `^4.0.0` | [docs/adapters/hono.md](docs/adapters/hono.md) |
272
+ | H3 / Nuxt | `@supabase/server/adapters/h3` | `^2.0.0` | [docs/adapters/h3.md](docs/adapters/h3.md) |
273
+ | Elysia | `@supabase/server/adapters/elysia` | `^1.4.0` | [docs/adapters/elysia.md](docs/adapters/elysia.md) |
274
+ | NestJS | `@supabase/server/adapters/nestjs` | `^10.0.0 \|\| ^11.0.0` | [docs/adapters/nestjs.md](docs/adapters/nestjs.md) |
274
275
 
275
276
  See the per-adapter docs above for setup, per-route auth, CORS, error handling, and other patterns.
276
277
 
@@ -316,6 +317,25 @@ app.listen(3000)
316
317
 
317
318
  The adapter does not handle CORS — use `@elysiajs/cors` for that.
318
319
 
320
+ ### NestJS
321
+
322
+ ```ts
323
+ import { Controller, Get, UseGuards } from '@nestjs/common'
324
+ import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
325
+ import type { SupabaseContext } from '@supabase/server'
326
+
327
+ @Controller('games')
328
+ @UseGuards(withSupabase({ auth: 'user' }))
329
+ export class GamesController {
330
+ @Get()
331
+ list(@SupabaseCtx() ctx: SupabaseContext) {
332
+ return ctx.supabase.from('favorite_games').select()
333
+ }
334
+ }
335
+ ```
336
+
337
+ See [docs/adapters/nestjs.md](docs/adapters/nestjs.md) for per-route auth, exception filters, CORS, and more.
338
+
319
339
  ## Primitives
320
340
 
321
341
  For when you need more control than `withSupabase` provides — multiple routes with different auth, custom response headers, or building your own wrapper.
@@ -465,6 +485,7 @@ No. `@supabase/ssr` handles cookie-based session management for frameworks like
465
485
  | `@supabase/server/adapters/hono` | `withSupabase` (Hono middleware) |
466
486
  | `@supabase/server/adapters/h3` | `withSupabase` (H3 / Nuxt middleware) |
467
487
  | `@supabase/server/adapters/elysia` | `withSupabase` (Elysia plugin) |
488
+ | `@supabase/server/adapters/nestjs` | `withSupabase` (NestJS guard), `SupabaseCtx` (param decorator) |
468
489
 
469
490
  ## Documentation
470
491
 
@@ -476,6 +497,7 @@ No. `@supabase/ssr` handles cookie-based session management for frameworks like
476
497
  | How do I use this with Hono? | [`docs/adapters/hono.md`](docs/adapters/hono.md) |
477
498
  | How do I use this with H3 / Nuxt? | [`docs/adapters/h3.md`](docs/adapters/h3.md) |
478
499
  | How do I use this with Elysia? | [`docs/adapters/elysia.md`](docs/adapters/elysia.md) |
500
+ | How do I use this with NestJS? | [`docs/adapters/nestjs.md`](docs/adapters/nestjs.md) |
479
501
  | How do I use low-level primitives for custom flows? | [`docs/core-primitives.md`](docs/core-primitives.md) |
480
502
  | How do environment variables work across runtimes? | [`docs/environment-variables.md`](docs/environment-variables.md) |
481
503
  | How do I handle errors? What codes exist? | [`docs/error-handling.md`](docs/error-handling.md) |
@@ -0,0 +1,118 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_create_supabase_context = require('../../create-supabase-context-CKv-4AIg.cjs');
3
+ let _nestjs_common = require("@nestjs/common");
4
+
5
+ //#region src/adapters/nestjs/middleware.ts
6
+ function toWebRequest(req) {
7
+ const headers = new Headers();
8
+ for (const [name, value] of Object.entries(req.headers ?? {})) {
9
+ if (name.startsWith(":")) continue;
10
+ if (Array.isArray(value)) headers.set(name, value.join(", "));
11
+ else if (value != null) headers.set(name, String(value));
12
+ }
13
+ return new Request(`http://nestjs.local${req.url ?? "/"}`, { headers });
14
+ }
15
+ /**
16
+ * NestJS guard that creates a {@link SupabaseContext} and stores it on the
17
+ * underlying request as `request.supabaseContext`.
18
+ *
19
+ * **HTTP-only.** The guard reads HTTP headers via `switchToHttp()` and throws
20
+ * if applied to an RPC or WebSocket handler — those transports must
21
+ * authenticate via context-specific mechanisms.
22
+ *
23
+ * Always runs, even if a previous guard already set the context. This matches
24
+ * Nest's guard order (global → controller → handler), so handler-level guards
25
+ * can tighten what a global guard set rather than being skipped.
26
+ *
27
+ * Throws `HttpException` on auth failure — the original `AuthError` is
28
+ * available via `cause`.
29
+ *
30
+ * @param config - Auth modes and optional environment overrides. CORS is excluded —
31
+ * use NestJS's built-in CORS (`app.enableCors()`).
32
+ * @returns A guard class that can be passed to `@UseGuards(...)`.
33
+ *
34
+ * @example App-wide auth via `app.useGlobalGuards()`
35
+ * ```ts
36
+ * import { NestFactory } from '@nestjs/core'
37
+ * import { withSupabase } from '@supabase/server/adapters/nestjs'
38
+ *
39
+ * const app = await NestFactory.create(AppModule)
40
+ * app.useGlobalGuards(new (withSupabase({ auth: 'user' }))())
41
+ * await app.listen(3000)
42
+ * ```
43
+ *
44
+ * @example Per-route auth via `@UseGuards(...)`
45
+ * ```ts
46
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
47
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
48
+ * import type { SupabaseContext } from '@supabase/server'
49
+ *
50
+ * @Controller('games')
51
+ * export class GamesController {
52
+ * @Get()
53
+ * @UseGuards(withSupabase({ auth: 'user' }))
54
+ * async list(@SupabaseCtx() ctx: SupabaseContext) {
55
+ * const { data } = await ctx.supabase.from('favorite_games').select()
56
+ * return data
57
+ * }
58
+ * }
59
+ * ```
60
+ */
61
+ function withSupabase(config) {
62
+ @((0, _nestjs_common.Injectable)()) class SupabaseAuthGuard {
63
+ async canActivate(executionContext) {
64
+ const contextType = executionContext.getType();
65
+ if (contextType !== "http") throw new _nestjs_common.HttpException({
66
+ message: `withSupabase guard only supports HTTP contexts (got '${contextType}'). Authenticate non-HTTP transports separately.`,
67
+ code: "unsupported_context"
68
+ }, 500);
69
+ const req = executionContext.switchToHttp().getRequest();
70
+ const { data: ctx, error } = await require_create_supabase_context.createSupabaseContext(toWebRequest(req), config);
71
+ if (error) throw new _nestjs_common.HttpException({
72
+ message: error.message,
73
+ code: error.code
74
+ }, error.status, { cause: error });
75
+ req.supabaseContext = ctx;
76
+ return true;
77
+ }
78
+ }
79
+ return SupabaseAuthGuard;
80
+ }
81
+
82
+ //#endregion
83
+ //#region src/adapters/nestjs/decorator.ts
84
+ /**
85
+ * NestJS param decorator that returns the {@link SupabaseContext} attached
86
+ * by `withSupabase()`. Pass a key (e.g. `'supabase'`, `'userClaims'`) to pull
87
+ * a single field, or no argument to receive the whole context.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
92
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
93
+ * import type { SupabaseContext } from '@supabase/server'
94
+ *
95
+ * @Controller('games')
96
+ * @UseGuards(withSupabase({ auth: 'user' }))
97
+ * export class GamesController {
98
+ * @Get()
99
+ * list(@SupabaseCtx() ctx: SupabaseContext) {
100
+ * return ctx.supabase.from('favorite_games').select()
101
+ * }
102
+ *
103
+ * @Get('me')
104
+ * me(@SupabaseCtx('userClaims') user: SupabaseContext['userClaims']) {
105
+ * return user
106
+ * }
107
+ * }
108
+ * ```
109
+ */
110
+ const SupabaseCtx = (0, _nestjs_common.createParamDecorator)((data, ctx) => {
111
+ const supabaseContext = ctx.switchToHttp().getRequest().supabaseContext;
112
+ if (data) return supabaseContext?.[data];
113
+ return supabaseContext;
114
+ });
115
+
116
+ //#endregion
117
+ exports.SupabaseCtx = SupabaseCtx;
118
+ exports.withSupabase = withSupabase;
@@ -0,0 +1,82 @@
1
+ import { d as SupabaseContext, m as WithSupabaseConfig } from "../../types-CwKZOVIv.cjs";
2
+ import { CanActivate, PipeTransform, Type } from "@nestjs/common";
3
+
4
+ //#region src/adapters/nestjs/middleware.d.ts
5
+ /**
6
+ * NestJS guard that creates a {@link SupabaseContext} and stores it on the
7
+ * underlying request as `request.supabaseContext`.
8
+ *
9
+ * **HTTP-only.** The guard reads HTTP headers via `switchToHttp()` and throws
10
+ * if applied to an RPC or WebSocket handler — those transports must
11
+ * authenticate via context-specific mechanisms.
12
+ *
13
+ * Always runs, even if a previous guard already set the context. This matches
14
+ * Nest's guard order (global → controller → handler), so handler-level guards
15
+ * can tighten what a global guard set rather than being skipped.
16
+ *
17
+ * Throws `HttpException` on auth failure — the original `AuthError` is
18
+ * available via `cause`.
19
+ *
20
+ * @param config - Auth modes and optional environment overrides. CORS is excluded —
21
+ * use NestJS's built-in CORS (`app.enableCors()`).
22
+ * @returns A guard class that can be passed to `@UseGuards(...)`.
23
+ *
24
+ * @example App-wide auth via `app.useGlobalGuards()`
25
+ * ```ts
26
+ * import { NestFactory } from '@nestjs/core'
27
+ * import { withSupabase } from '@supabase/server/adapters/nestjs'
28
+ *
29
+ * const app = await NestFactory.create(AppModule)
30
+ * app.useGlobalGuards(new (withSupabase({ auth: 'user' }))())
31
+ * await app.listen(3000)
32
+ * ```
33
+ *
34
+ * @example Per-route auth via `@UseGuards(...)`
35
+ * ```ts
36
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
37
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
38
+ * import type { SupabaseContext } from '@supabase/server'
39
+ *
40
+ * @Controller('games')
41
+ * export class GamesController {
42
+ * @Get()
43
+ * @UseGuards(withSupabase({ auth: 'user' }))
44
+ * async list(@SupabaseCtx() ctx: SupabaseContext) {
45
+ * const { data } = await ctx.supabase.from('favorite_games').select()
46
+ * return data
47
+ * }
48
+ * }
49
+ * ```
50
+ */
51
+ declare function withSupabase(config?: Omit<WithSupabaseConfig, 'cors'>): Type<CanActivate>;
52
+ //#endregion
53
+ //#region src/adapters/nestjs/decorator.d.ts
54
+ /**
55
+ * NestJS param decorator that returns the {@link SupabaseContext} attached
56
+ * by `withSupabase()`. Pass a key (e.g. `'supabase'`, `'userClaims'`) to pull
57
+ * a single field, or no argument to receive the whole context.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
62
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
63
+ * import type { SupabaseContext } from '@supabase/server'
64
+ *
65
+ * @Controller('games')
66
+ * @UseGuards(withSupabase({ auth: 'user' }))
67
+ * export class GamesController {
68
+ * @Get()
69
+ * list(@SupabaseCtx() ctx: SupabaseContext) {
70
+ * return ctx.supabase.from('favorite_games').select()
71
+ * }
72
+ *
73
+ * @Get('me')
74
+ * me(@SupabaseCtx('userClaims') user: SupabaseContext['userClaims']) {
75
+ * return user
76
+ * }
77
+ * }
78
+ * ```
79
+ */
80
+ declare const SupabaseCtx: (data?: keyof SupabaseContext, ...pipes: (Type<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
81
+ //#endregion
82
+ export { SupabaseCtx, withSupabase };
@@ -0,0 +1,82 @@
1
+ import { d as SupabaseContext, m as WithSupabaseConfig } from "../../types-DbvLfq25.mjs";
2
+ import { CanActivate, PipeTransform, Type } from "@nestjs/common";
3
+
4
+ //#region src/adapters/nestjs/middleware.d.ts
5
+ /**
6
+ * NestJS guard that creates a {@link SupabaseContext} and stores it on the
7
+ * underlying request as `request.supabaseContext`.
8
+ *
9
+ * **HTTP-only.** The guard reads HTTP headers via `switchToHttp()` and throws
10
+ * if applied to an RPC or WebSocket handler — those transports must
11
+ * authenticate via context-specific mechanisms.
12
+ *
13
+ * Always runs, even if a previous guard already set the context. This matches
14
+ * Nest's guard order (global → controller → handler), so handler-level guards
15
+ * can tighten what a global guard set rather than being skipped.
16
+ *
17
+ * Throws `HttpException` on auth failure — the original `AuthError` is
18
+ * available via `cause`.
19
+ *
20
+ * @param config - Auth modes and optional environment overrides. CORS is excluded —
21
+ * use NestJS's built-in CORS (`app.enableCors()`).
22
+ * @returns A guard class that can be passed to `@UseGuards(...)`.
23
+ *
24
+ * @example App-wide auth via `app.useGlobalGuards()`
25
+ * ```ts
26
+ * import { NestFactory } from '@nestjs/core'
27
+ * import { withSupabase } from '@supabase/server/adapters/nestjs'
28
+ *
29
+ * const app = await NestFactory.create(AppModule)
30
+ * app.useGlobalGuards(new (withSupabase({ auth: 'user' }))())
31
+ * await app.listen(3000)
32
+ * ```
33
+ *
34
+ * @example Per-route auth via `@UseGuards(...)`
35
+ * ```ts
36
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
37
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
38
+ * import type { SupabaseContext } from '@supabase/server'
39
+ *
40
+ * @Controller('games')
41
+ * export class GamesController {
42
+ * @Get()
43
+ * @UseGuards(withSupabase({ auth: 'user' }))
44
+ * async list(@SupabaseCtx() ctx: SupabaseContext) {
45
+ * const { data } = await ctx.supabase.from('favorite_games').select()
46
+ * return data
47
+ * }
48
+ * }
49
+ * ```
50
+ */
51
+ declare function withSupabase(config?: Omit<WithSupabaseConfig, 'cors'>): Type<CanActivate>;
52
+ //#endregion
53
+ //#region src/adapters/nestjs/decorator.d.ts
54
+ /**
55
+ * NestJS param decorator that returns the {@link SupabaseContext} attached
56
+ * by `withSupabase()`. Pass a key (e.g. `'supabase'`, `'userClaims'`) to pull
57
+ * a single field, or no argument to receive the whole context.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
62
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
63
+ * import type { SupabaseContext } from '@supabase/server'
64
+ *
65
+ * @Controller('games')
66
+ * @UseGuards(withSupabase({ auth: 'user' }))
67
+ * export class GamesController {
68
+ * @Get()
69
+ * list(@SupabaseCtx() ctx: SupabaseContext) {
70
+ * return ctx.supabase.from('favorite_games').select()
71
+ * }
72
+ *
73
+ * @Get('me')
74
+ * me(@SupabaseCtx('userClaims') user: SupabaseContext['userClaims']) {
75
+ * return user
76
+ * }
77
+ * }
78
+ * ```
79
+ */
80
+ declare const SupabaseCtx: (data?: keyof SupabaseContext, ...pipes: (Type<PipeTransform> | PipeTransform)[]) => ParameterDecorator;
81
+ //#endregion
82
+ export { SupabaseCtx, withSupabase };
@@ -0,0 +1,116 @@
1
+ import { t as createSupabaseContext } from "../../create-supabase-context-BxSEJN8a.mjs";
2
+ import { HttpException, Injectable, createParamDecorator } from "@nestjs/common";
3
+
4
+ //#region src/adapters/nestjs/middleware.ts
5
+ function toWebRequest(req) {
6
+ const headers = new Headers();
7
+ for (const [name, value] of Object.entries(req.headers ?? {})) {
8
+ if (name.startsWith(":")) continue;
9
+ if (Array.isArray(value)) headers.set(name, value.join(", "));
10
+ else if (value != null) headers.set(name, String(value));
11
+ }
12
+ return new Request(`http://nestjs.local${req.url ?? "/"}`, { headers });
13
+ }
14
+ /**
15
+ * NestJS guard that creates a {@link SupabaseContext} and stores it on the
16
+ * underlying request as `request.supabaseContext`.
17
+ *
18
+ * **HTTP-only.** The guard reads HTTP headers via `switchToHttp()` and throws
19
+ * if applied to an RPC or WebSocket handler — those transports must
20
+ * authenticate via context-specific mechanisms.
21
+ *
22
+ * Always runs, even if a previous guard already set the context. This matches
23
+ * Nest's guard order (global → controller → handler), so handler-level guards
24
+ * can tighten what a global guard set rather than being skipped.
25
+ *
26
+ * Throws `HttpException` on auth failure — the original `AuthError` is
27
+ * available via `cause`.
28
+ *
29
+ * @param config - Auth modes and optional environment overrides. CORS is excluded —
30
+ * use NestJS's built-in CORS (`app.enableCors()`).
31
+ * @returns A guard class that can be passed to `@UseGuards(...)`.
32
+ *
33
+ * @example App-wide auth via `app.useGlobalGuards()`
34
+ * ```ts
35
+ * import { NestFactory } from '@nestjs/core'
36
+ * import { withSupabase } from '@supabase/server/adapters/nestjs'
37
+ *
38
+ * const app = await NestFactory.create(AppModule)
39
+ * app.useGlobalGuards(new (withSupabase({ auth: 'user' }))())
40
+ * await app.listen(3000)
41
+ * ```
42
+ *
43
+ * @example Per-route auth via `@UseGuards(...)`
44
+ * ```ts
45
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
46
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
47
+ * import type { SupabaseContext } from '@supabase/server'
48
+ *
49
+ * @Controller('games')
50
+ * export class GamesController {
51
+ * @Get()
52
+ * @UseGuards(withSupabase({ auth: 'user' }))
53
+ * async list(@SupabaseCtx() ctx: SupabaseContext) {
54
+ * const { data } = await ctx.supabase.from('favorite_games').select()
55
+ * return data
56
+ * }
57
+ * }
58
+ * ```
59
+ */
60
+ function withSupabase(config) {
61
+ @Injectable() class SupabaseAuthGuard {
62
+ async canActivate(executionContext) {
63
+ const contextType = executionContext.getType();
64
+ if (contextType !== "http") throw new HttpException({
65
+ message: `withSupabase guard only supports HTTP contexts (got '${contextType}'). Authenticate non-HTTP transports separately.`,
66
+ code: "unsupported_context"
67
+ }, 500);
68
+ const req = executionContext.switchToHttp().getRequest();
69
+ const { data: ctx, error } = await createSupabaseContext(toWebRequest(req), config);
70
+ if (error) throw new HttpException({
71
+ message: error.message,
72
+ code: error.code
73
+ }, error.status, { cause: error });
74
+ req.supabaseContext = ctx;
75
+ return true;
76
+ }
77
+ }
78
+ return SupabaseAuthGuard;
79
+ }
80
+
81
+ //#endregion
82
+ //#region src/adapters/nestjs/decorator.ts
83
+ /**
84
+ * NestJS param decorator that returns the {@link SupabaseContext} attached
85
+ * by `withSupabase()`. Pass a key (e.g. `'supabase'`, `'userClaims'`) to pull
86
+ * a single field, or no argument to receive the whole context.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * import { Controller, Get, UseGuards } from '@nestjs/common'
91
+ * import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
92
+ * import type { SupabaseContext } from '@supabase/server'
93
+ *
94
+ * @Controller('games')
95
+ * @UseGuards(withSupabase({ auth: 'user' }))
96
+ * export class GamesController {
97
+ * @Get()
98
+ * list(@SupabaseCtx() ctx: SupabaseContext) {
99
+ * return ctx.supabase.from('favorite_games').select()
100
+ * }
101
+ *
102
+ * @Get('me')
103
+ * me(@SupabaseCtx('userClaims') user: SupabaseContext['userClaims']) {
104
+ * return user
105
+ * }
106
+ * }
107
+ * ```
108
+ */
109
+ const SupabaseCtx = createParamDecorator((data, ctx) => {
110
+ const supabaseContext = ctx.switchToHttp().getRequest().supabaseContext;
111
+ if (data) return supabaseContext?.[data];
112
+ return supabaseContext;
113
+ });
114
+
115
+ //#endregion
116
+ export { SupabaseCtx, withSupabase };
@@ -0,0 +1,204 @@
1
+ # NestJS Adapter
2
+
3
+ ## Setup
4
+
5
+ Install NestJS as a peer dependency:
6
+
7
+ ```bash
8
+ pnpm add @nestjs/common @nestjs/core
9
+ ```
10
+
11
+ The adapter exports `withSupabase` (a guard factory) and `SupabaseCtx` (a param decorator). Together they replace the `c.var.supabaseContext` / `event.context.supabaseContext` patterns from the Hono and H3 adapters.
12
+
13
+ `withSupabase(config)` returns a `CanActivate` guard class. The guard reads the underlying request (Express or Fastify), verifies credentials with `@supabase/server/core`, and attaches the resulting `SupabaseContext` to `request.supabaseContext`. From any handler you can pull it out with `@SupabaseCtx()`.
14
+
15
+ ## Basic controller with auth
16
+
17
+ ```ts
18
+ // games.controller.ts
19
+ import { Controller, Get, UseGuards } from '@nestjs/common'
20
+ import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
21
+ import type { SupabaseContext } from '@supabase/server'
22
+
23
+ @Controller('games')
24
+ @UseGuards(withSupabase({ auth: 'user' }))
25
+ export class GamesController {
26
+ @Get()
27
+ async list(@SupabaseCtx() ctx: SupabaseContext) {
28
+ const { data } = await ctx.supabase.from('favorite_games').select()
29
+ return data
30
+ }
31
+
32
+ @Get('me')
33
+ me(@SupabaseCtx('userClaims') user: SupabaseContext['userClaims']) {
34
+ return user
35
+ }
36
+ }
37
+ ```
38
+
39
+ `@SupabaseCtx()` returns the entire `SupabaseContext` (`supabase`, `supabaseAdmin`, `userClaims`, `jwtClaims`, `authMode`, `authKeyName`). Pass a key (`@SupabaseCtx('supabase')`) to extract a single field.
40
+
41
+ ### Typing your database
42
+
43
+ The guard does not thread a `Database` generic, so `@SupabaseCtx()` resolves to `SupabaseContext<unknown>` by default. To get typed table access, annotate the parameter at the handler:
44
+
45
+ ```ts
46
+ import type { SupabaseContext } from '@supabase/server'
47
+ import type { Database } from './database.types'
48
+
49
+ @Get()
50
+ async list(@SupabaseCtx() ctx: SupabaseContext<Database>) {
51
+ const { data } = await ctx.supabase.from('favorite_games').select()
52
+ return data
53
+ }
54
+ ```
55
+
56
+ ## Per-route auth
57
+
58
+ Apply different auth modes per controller or per handler — the closest `@UseGuards()` wins:
59
+
60
+ ```ts
61
+ import { Controller, Get, Post, UseGuards } from '@nestjs/common'
62
+ import { withSupabase, SupabaseCtx } from '@supabase/server/adapters/nestjs'
63
+ import type { SupabaseContext } from '@supabase/server'
64
+
65
+ @Controller()
66
+ export class AppController {
67
+ // Public — no guard
68
+ @Get('health')
69
+ health() {
70
+ return { status: 'ok' }
71
+ }
72
+
73
+ // User-authenticated route
74
+ @Get('todos')
75
+ @UseGuards(withSupabase({ auth: 'user' }))
76
+ async todos(@SupabaseCtx() ctx: SupabaseContext) {
77
+ const { data } = await ctx.supabase.from('todos').select()
78
+ return data
79
+ }
80
+
81
+ // Secret-key-protected admin route
82
+ @Post('admin/sync')
83
+ @UseGuards(withSupabase({ auth: 'secret' }))
84
+ async sync(@SupabaseCtx() ctx: SupabaseContext) {
85
+ const { data } = await ctx.supabaseAdmin
86
+ .from('audit_log')
87
+ .insert({ action: 'sync' })
88
+ return data
89
+ }
90
+
91
+ // Dual auth — users or services
92
+ @Get('reports')
93
+ @UseGuards(withSupabase({ auth: ['user', 'secret'] }))
94
+ reports(@SupabaseCtx('authMode') authMode: SupabaseContext['authMode']) {
95
+ return { authMode }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## App-wide guard
101
+
102
+ Apply the guard globally with `app.useGlobalGuards()`:
103
+
104
+ ```ts
105
+ // main.ts
106
+ import { NestFactory } from '@nestjs/core'
107
+ import { withSupabase } from '@supabase/server/adapters/nestjs'
108
+ import { AppModule } from './app.module'
109
+
110
+ async function bootstrap() {
111
+ const app = await NestFactory.create(AppModule)
112
+ app.useGlobalGuards(new (withSupabase({ auth: 'user' }))())
113
+ await app.listen(3000)
114
+ }
115
+ bootstrap()
116
+ ```
117
+
118
+ ## Multiple guards
119
+
120
+ `withSupabase` always runs, even if a previous guard already set `request.supabaseContext`. NestJS executes guards in order (global → controller → handler), so a handler-level guard naturally tightens what a global guard set: the later guard re-authenticates with its own config and either rejects the request or overwrites the context. The innermost guard wins.
121
+
122
+ If you need different auth per route, prefer per-route `@UseGuards(...)` without a global guard.
123
+
124
+ ## CORS
125
+
126
+ The NestJS adapter does not handle CORS. Use NestJS's built-in CORS:
127
+
128
+ ```ts
129
+ // main.ts
130
+ import { NestFactory } from '@nestjs/core'
131
+ import { AppModule } from './app.module'
132
+
133
+ async function bootstrap() {
134
+ const app = await NestFactory.create(AppModule)
135
+ app.enableCors({ origin: 'https://myapp.com' })
136
+ await app.listen(3000)
137
+ }
138
+ bootstrap()
139
+ ```
140
+
141
+ The `cors` option is excluded from `WithSupabaseConfig` for this adapter.
142
+
143
+ ## Error handling
144
+
145
+ When auth fails, the adapter throws a NestJS `HttpException`. The original `AuthError` is available via `cause`. Add an exception filter to format the response:
146
+
147
+ ```ts
148
+ // supabase-auth.filter.ts
149
+ import {
150
+ ArgumentsHost,
151
+ Catch,
152
+ ExceptionFilter,
153
+ HttpException,
154
+ } from '@nestjs/common'
155
+ import { AuthError } from '@supabase/server'
156
+ import type { Response } from 'express'
157
+
158
+ @Catch(HttpException)
159
+ export class SupabaseAuthFilter implements ExceptionFilter {
160
+ catch(exception: HttpException, host: ArgumentsHost) {
161
+ const cause = exception.cause
162
+ if (!(cause instanceof AuthError)) throw exception
163
+
164
+ const res = host.switchToHttp().getResponse<Response>()
165
+ res.status(cause.status).json({
166
+ error: cause.message,
167
+ code: cause.code,
168
+ })
169
+ }
170
+ }
171
+ ```
172
+
173
+ Register it globally:
174
+
175
+ ```ts
176
+ // main.ts
177
+ app.useGlobalFilters(new SupabaseAuthFilter())
178
+ ```
179
+
180
+ ## Environment overrides
181
+
182
+ Pass `env` to override auto-detected environment variables:
183
+
184
+ ```ts
185
+ @UseGuards(
186
+ withSupabase({
187
+ auth: 'user',
188
+ env: { url: 'http://localhost:54321' },
189
+ }),
190
+ )
191
+ ```
192
+
193
+ ## Supabase client options
194
+
195
+ Forward options to the underlying `createClient()` calls:
196
+
197
+ ```ts
198
+ @UseGuards(
199
+ withSupabase({
200
+ auth: 'user',
201
+ supabaseOptions: { db: { schema: 'api' } },
202
+ }),
203
+ )
204
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/server",
3
- "version": "1.1.0-rc.66",
3
+ "version": "1.2.0-rc.69",
4
4
  "description": "Server-side utilities for Supabase. Handles auth, client creation, and context injection so you write business logic, not boilerplate.",
5
5
  "keywords": [
6
6
  "edge",
@@ -44,6 +44,11 @@
44
44
  "import": "./dist/adapters/elysia/index.mjs",
45
45
  "require": "./dist/adapters/elysia/index.cjs"
46
46
  },
47
+ "./adapters/nestjs": {
48
+ "types": "./dist/adapters/nestjs/index.d.mts",
49
+ "import": "./dist/adapters/nestjs/index.mjs",
50
+ "require": "./dist/adapters/nestjs/index.cjs"
51
+ },
47
52
  "./package.json": "./package.json"
48
53
  },
49
54
  "main": "./dist/index.cjs",
@@ -68,19 +73,23 @@
68
73
  "prepare": "simple-git-hooks",
69
74
  "test": "vitest run",
70
75
  "test:watch": "vitest",
71
- "typecheck": "tsc --noEmit"
76
+ "typecheck": "tsc --noEmit && tsc --noEmit -p src/adapters/nestjs"
72
77
  },
73
78
  "simple-git-hooks": {
74
79
  "pre-commit": "pnpm pretty-quick --staged",
75
80
  "commit-msg": "pnpm commitlint --edit \"$1\""
76
81
  },
77
82
  "peerDependencies": {
83
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
78
84
  "@supabase/supabase-js": "^2.0.0",
79
85
  "h3": "^2.0.0",
80
86
  "hono": "^4.0.0",
81
87
  "elysia": "^1.4.0"
82
88
  },
83
89
  "peerDependenciesMeta": {
90
+ "@nestjs/common": {
91
+ "optional": true
92
+ },
84
93
  "h3": {
85
94
  "optional": true
86
95
  },
@@ -94,18 +103,29 @@
94
103
  "devDependencies": {
95
104
  "@commitlint/cli": "^20.4.2",
96
105
  "@commitlint/config-conventional": "^20.4.2",
106
+ "@nestjs/common": "^11.1.19",
107
+ "@nestjs/core": "^11.1.19",
108
+ "@nestjs/platform-express": "^11.1.19",
109
+ "@nestjs/platform-fastify": "^11.1.19",
110
+ "@nestjs/testing": "^11.1.19",
97
111
  "@supabase/supabase-js": "^2.105.4",
112
+ "@swc/core": "^1.15.33",
113
+ "@types/supertest": "^7.2.0",
98
114
  "eslint": "^10.0.2",
99
115
  "elysia": "^1.4.0",
100
116
  "h3": "2.0.1-rc.20",
101
117
  "hono": "^4.12.5",
102
118
  "prettier": "3.8.1",
103
119
  "pretty-quick": "^4.2.2",
120
+ "reflect-metadata": "^0.2.2",
121
+ "rxjs": "^7.8.2",
104
122
  "simple-git-hooks": "^2.13.1",
123
+ "supertest": "^7.2.2",
105
124
  "tsdown": "^0.20.3",
106
125
  "typedoc": "^0.28.19",
107
126
  "typescript": "^5.9.3",
108
127
  "typescript-eslint": "^8.56.1",
128
+ "unplugin-swc": "^1.5.9",
109
129
  "vitest": "^4.0.18"
110
130
  },
111
131
  "dependencies": {