This document describes the backend architecture of the CityU Vet Sim project, primarily built with tRPC and integrated with Next.js API Routes, Drizzle ORM, and better-auth.
The backend API is built using tRPC, which provides end-to-end type safety between the frontend and backend. The core tRPC server setup is defined in src/server/api/trpc.ts.
The createTRPCContext function is responsible for creating the context object that is available to all tRPC procedures. This context includes essential resources for handling requests.
db: The Drizzle ORM database instance, allowing direct database interactions.session: The user session object, retrieved using auth.api.getSession, which provides authentication information.opts.headers: Request headers, useful for various purposes including authentication.
// src/server/api/trpc.ts
export const createTRPCContext = async (opts: { headers: Headers }) => {
const session = await auth.api.getSession({
headers: opts.headers,
});
return {
db,
session,
...opts,
};
};
The tRPC server is initialized with a transformer (SuperJSON for serializing/deserializing complex types) and a custom error formatter that includes Zod validation errors.
// src/server/api/trpc.ts
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
tRPC procedures define the API endpoints. The project uses two main types of procedures:
publicProcedure: For endpoints that do not require authentication. While the session is available in the context, access to ctx.session.user is not guaranteed.protectedProcedure: For endpoints that require an authenticated user. This procedure includes a middleware that checks for a valid user session and throws an UNAUTHORIZED error if none is found, ensuring secure access to sensitive data and operations.Both procedures utilize timingMiddleware for logging execution times and introducing an artificial delay in development environments to simulate network latency.
// src/server/api/trpc.ts
export const publicProcedure = t.procedure.use(timingMiddleware);
export const protectedProcedure = t.procedure
.use(timingMiddleware)
.use(({ ctx, next }) => {
if (!ctx.session?.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
},
});
});
tRPC routers organize related procedures into logical modules.
src/server/api/root.ts)The appRouter in src/server/api/root.ts is the main router that aggregates all domain-specific routers. This creates a single, unified API interface for the frontend.
// src/server/api/root.ts
import { createTRPCRouter } from "@/server/api/trpc";
import { postRouter } from "@/server/api/routers/post";
// ... other router imports
export const appRouter = createTRPCRouter({
post: postRouter,
voice: voiceRouter,
evaluation: evaluationRouter,
user: userRouter,
notes: notesRouter,
elevenlabs: elevenlabsRouter,
video: videoRouter,
});
export type AppRouter = typeof appRouter;
src/server/api/routers/)This directory contains individual router files, each responsible for a specific domain or feature within the application. These routers define queries (for fetching data) and mutations (for modifying data) using publicProcedure or protectedProcedure.
elevenlabs.ts: Procedures for integrating with ElevenLabs, e.g., generating signed URLs for WebRTC conversations.evaluation.ts: API endpoints for handling simulation evaluation data.notes.ts: Procedures for creating, retrieving, and updating simulation notes.post.ts: An example router (boilerplate).user.ts: User-related operations, such as fetching user profiles or updating user settings (e.g., personality traits).video.ts: Endpoints for retrieving video URLs from Vercel Blob storage.voice.ts: General voice-related operations, potentially for processing and responding to voice inputs.Next.js API Routes are used to expose the tRPC server and handle authentication callbacks.
src/app/api/trpc/[trpc]/route.ts: This route acts as the entry point for all tRPC API requests. It dynamically handles tRPC procedures and integrates them with the Next.js request/response cycle.src/app/(auth)/api/auth/[...all]/route.ts: This route handles all authentication-related API calls and callbacks managed by better-auth. It serves as the bridge between the client-side authentication flow and the server-side betterAuth instance.The backend integrates with better-auth for robust user authentication and authorization.
src/lib/auth.ts: This file configures the server-side betterAuth instance. It uses drizzleAdapter to connect better-auth with the Drizzle ORM, storing user, session, and account information in the PostgreSQL database. It also defines baseURLs for various deployment environments.createTRPCContext: As mentioned, the tRPC context is populated with the user session, allowing protectedProcedure to enforce authentication for specific API endpoints.Various backend utility functions support the core logic.
src/utils/convertWebmToMp3.ts: Converts WebM audio buffers to MP3 format using fluent-ffmpeg. This is essential for processing voice recordings from the frontend before sending them to services like ElevenLabs.src/utils/createPrompt.ts: A crucial utility that dynamically generates highly detailed AI prompts for the ElevenLabs integration. These prompts define the persona, strict instructions, and case-specific context for the AI pet owner, ensuring tailored and effective responses in the simulation.src/lib/blob.ts: Functions (getVideoUrl, getVideoUrls) to interact with Vercel Blob storage, retrieving public URLs for video assets. This is used by the videoRouter to serve video content.Consider a user submitting a voice message in a simulation:
api.voice.processAudio.mutate(...)).src/app/api/trpc/[trpc]/route.ts API route, which then routes it to the relevant tRPC procedure (e.g., within voiceRouter or elevenlabsRouter).ctx.db to interact with the database (e.g., save simulation notes) and ctx.session to verify the user's authentication status (if it's a protectedProcedure).src/utils/createPrompt.ts to generate a context-rich prompt for an AI service (like OpenAI). It then interacts with external APIs (e.g., ElevenLabs via direct API calls or specialized SDKs) to get a textual response, which is then converted back to speech.ctx.db) is used to update the PostgreSQL database according to the schema defined in src/server/db/schema.ts.