Marketplace Starter Kit

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 protectedProcedures 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 TypeDescription
publicProcedureNo auth required
protectedProcedureRequires 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()