Authentication
How Supabase authentication is implemented and integrated into protected API routes.
Authentication Architecture
This app uses tRPC for building a fully typed API—eliminating the need for REST or GraphQL. Each API procedure is colocated with the logic that owns it, and Supabase provides auth context for protecting routes.
File Structure
Your tRPC implementation lives in:
src/server/api/
├── routers/
│ ├── user.ts
│ ├── otp.ts
│ ├── booking.ts
│ └── ...
├── root.ts # Composes all routers
└── trpc.ts # Base router helpers: public, protected
Supabase Authentication
All protectedProcedure
s use a getUserOrThrow
helper based on Supabase sessions:
const user = await getUserOrThrow(ctx); // throws if not logged in
This pattern secures all API calls by default. For more, see the Supabase Auth Docs →
Authenticated API Integration
booking.ts
Defines protected mutations for creating bookings:
create: protectedProcedure
.input(BookingCreateSchema)
.mutation(async ({ ctx, input }) => {
const user = await getUserOrThrow(ctx);
return ctx.db.booking.create({ data: { ...input, userId: user.id } });
});
user.ts
Allows querying and updating user profile data:
me: protectedProcedure.query(async ({ ctx }) => {
const user = await getUserOrThrow(ctx);
return ctx.db.user.findUnique({ where: { id: user.id } });
});
otp.ts
Handles custom OTP verification flow using Supabase sessions:
verify: publicProcedure
.input(OtpVerifySchema)
.mutation(async ({ ctx, input }) => {
return await verifyOtpToken(input.token, ctx.supabase);
});
listing.ts
Defines CRUD for marketplace listings, scoped to authenticated users:
create: protectedProcedure
.input(ListingSchema)
.mutation(async ({ ctx, input }) => {
const user = await getUserOrThrow(ctx);
return ctx.db.listing.create({ data: { ...input, userId: user.id } });
});
Procedure Types
Procedure Type | Description |
---|---|
publicProcedure | No auth required |
protectedProcedure | Requires Supabase session (via ctx ) |
Frontend Usage
You can call any backend procedure with auto-complete:
const { data } = api.user.me.useQuery();
const mutation = api.booking.create.useMutation();
Input Validation
All input()
methods use Zod schemas for end-to-end validation. Example:
const BookingCreateSchema = z.object({
listingId: z.string().uuid(),
startTime: z.date(),
endTime: z.date(),
});
Error Handling
All errors use TRPCError
for consistent frontend response mapping:
throw new TRPCError({ code: "NOT_FOUND", message: "Listing not found" });
Summary
- Routers live under
src/server/api/routers
- Supabase is the source of truth for auth
- All inputs validated with Zod
protectedProcedure
ensures user sessions- Frontend is fully typed with
api.*.useQuery()