Architecture Deep Dive: Technical Implementation Details - Part 1
Architecture Deep Dive: Technical Implementation Details - Part 1
Comprehensive technical breakdown of TS Starter's architecture patterns, data flow, and implementation details
Core Architecture Patterns
1. Internal Services Architecture: SQLite DB → Drizzle → Better Auth → oRPC → TanStack Start
TS Starter's internal services follow a layered architecture pattern for secure, type-safe database operations. There are two distinct types of server-side routes:
- TanStack Start Server Routes: All routes under
src/routes/api/that usecreateFileRoute - oRPC Server Routes: All routes under
src/server/routes/that define oRPC procedures
2. Step-by-Step Implementation
Step 1: Client Component Initiates Request
// src/routes/_authed/task.tsx
import { orpc } from "~/lib/orpc";
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
export function TaskComponent() {
// INITIAL REQUEST: User clicks button to create task
const handleCreateTask = async (taskData) => {
// This is where the request actually starts!
await createTaskMutation.mutateAsync({
text: "Complete project documentation",
priority: "high",
hrs: 4,
isPublic: false
});
};
// INITIAL REQUEST: Component loads and fetches tasks
const { data: tasks } = useSuspenseQuery({
queryKey: ["tasks"] as const,
queryFn: () => orpc.task.getAll.call(), // ← Request initiated here
staleTime: 1000,
});
// INITIAL REQUEST: User submits form
const createTaskMutation = useMutation({
mutationFn: (data) => orpc.task.create.call(data), // ← Request initiated here
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["tasks"] });
},
});
return (
<div>
<button onClick={() => handleCreateTask()}>
Create Task
</button>
{tasks?.map(task => (
<div key={task.id}>{task.text}</div>
))}
</div>
);
}
Step 2: oRPC Client Handles Request
// src/lib/orpc.ts
const getORPCClient = createIsomorphicFn()
.server(() =>
createRouterClient(serverRouter, {
context: async () => {
const req = getWebRequest();
// Use the server oRPC context to include the authenticated session
const ctx = await createServerOrpcContext({
headers: req.headers,
req,
});
return ctx;
},
}),
)
.client((): RouterClient<typeof serverRouter> => {
const link = new RPCLink({
url: new URL("/api/rpc", import.meta.env.VITE_BASE_URL || window.location.origin),
fetch: (input, init) =>
fetch(input, {
...init,
credentials: "include",
}),
});
return createORPCClient(link);
});
export const client: RouterClient<typeof serverRouter> = getORPCClient();
export const orpc = createORPCReactQueryUtils(client);
Step 3: TanStack Start Server Route Receives oRPC Request
// src/routes/api/rpc.$.ts
import { createFileRoute } from "@tanstack/react-router";
import { serverHandler } from "~/server/handler";
import { auth } from "~/lib/auth";
async function handle({ request }: { request: Request }) {
// Get session data from the request headers
const session = await auth.api.getSession({
headers: request.headers,
query: {
disableCookieCache: true,
},
});
const { response } = await serverHandler.handle(request, {
context: {
session, // This matches the expected { session: {...}, user: {...} } | null structure
},
prefix: "/api/rpc",
});
return response ?? new Response("Not Found", { status: 404 });
}
export const Route = createFileRoute("/api/rpc/$")({
server: {
handlers: {
DELETE: handle,
GET: handle,
HEAD: handle,
PATCH: handle,
POST: handle,
PUT: handle,
},
},
});
// src/server/handler.ts - This creates the serverHandler that handle() calls
import { RPCHandler, CompressionPlugin } from "@orpc/server/fetch";
import { CORSPlugin } from "@orpc/server/plugins";
import { serverRouter } from "./routes";
export const serverHandler = new RPCHandler(serverRouter, {
plugins: [new CORSPlugin(), new CompressionPlugin()],
});
**3.1. The Connection Chain Explained
The handle function in rpc.$.ts connects to serverHandler through this flow:
rpc.$.ts (handle function)
↓
calls serverHandler.handle()
↓
serverHandler (from ~/server/handler)
↓
is RPCHandler with plugins
↓
serverRouter (from ~/server/routes)
↓
contains all your oRPC procedures
How it works:
- HTTP Request arrives at
/api/rpc/* - TanStack Start routes to
rpc.$.ts handle()function callsserverHandler.handle(request, context)serverHandlerprocesses the request through oRPC procedures- Response flows back through the chain
Step 4: oRPC Procedure with Authentication
// src/server/orpc.ts
import { auth } from "~/lib/auth";
// Create context with Better Auth session
export const createContext = async (opts: { headers: Headers; req: Request }) => {
let session = null;
try {
// Better Auth provides the session
session = await auth.api.getSession({ headers: opts.headers });
} catch (error) {
console.warn("Auth failed (SSR-safe):", error.message);
}
return { session };
};
// Protected procedures require authentication
const requireAuth = o.middleware(async ({ context, next }) => {
if (!context.session?.user) {
// Graceful fallback during SSR
return next({ context: { session: context.session } });
}
return next({ context: { session: context.session } });
});
export const protectedProcedure = publicProcedure.use(requireAuth);
Step 5: Business Logic with Database Access
// src/server/routes/task.ts
import { protectedProcedure, taskPublisher } from "../orpc";
import { db } from "~/db";
import { tasks } from "~/db/schema";
import z from "zod";
export const taskRouter = {
create: protectedProcedure
.input(z.object({
text: z.string(),
details: z.string().optional(),
priority: z.enum(["high", "medium", "low"]).default("medium"),
hrs: z.number().min(1).max(8).default(1)
}))
.handler(async ({ input, context }) => {
// context.session.user is guaranteed to exist here
const task = await db.insert(tasks).values({
id: crypto.randomUUID(),
text: input.text,
details: input.details || "",
priority: input.priority,
hrs: input.hrs,
status: "Not Started",
userId: context.session.user.id, // Secure user association
createdAt: new Date(),
updatedAt: new Date(),
}).returning();
// Publish real-time event
taskPublisher.publish("task-created", {
taskId: task[0].id,
userId: context.session.user.id,
task: task[0],
});
return task[0];
}),
getAll: protectedProcedure.handler(async ({ context }) => {
// Get user's own tasks
const userTasks = await db.query.tasks.findMany({
where: eq(tasks.userId, context.session.user.id),
});
// Get public tasks from other users
const publicTasks = await db.query.tasks.findMany({
where: and(
eq(tasks.isPublic, true),
ne(tasks.userId, context.session.user.id),
),
});
return { tasks: [...userTasks, ...publicTasks] };
}),
};
Step 6: Drizzle ORM Executes SQLite Query
// src/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
export const tasks = sqliteTable("tasks", {
id: text("id").primaryKey(),
text: text("text").notNull(),
details: text("details"),
priority: text("priority").notNull().default("medium"),
status: text("status").notNull().default("Not Started"),
hrs: integer("hrs").notNull().default(1),
isPublic: integer("isPublic", { mode: "boolean" }).default(false),
userId: text("userId").notNull().references(() => users.id),
createdAt: integer("createdAt", { mode: "timestamp" }).notNull(),
updatedAt: integer("updatedAt", { mode: "timestamp" }).notNull(),
refLink: text("refLink"),
});
// src/db/index.ts
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
const client = createClient({
url: env.DATABASE_URL, // SQLite file or Turso URL
authToken: env.DATABASE_AUTH_TOKEN,
});
export const db = drizzle(client, { schema });
Step 7: Response Flows Back Through the Chain
// Data flows back: Database → Drizzle → oRPC → Client → React Query → UI
// 1. Database returns result
const task = await db.insert(tasks).values({...}).returning();
// 2. oRPC procedure returns data
return task[0];
// 3. Client receives response through oRPC
const result = await orpc.task.create.call({
text: "New task",
priority: "high",
hrs: 2,
isPublic: false
});
// 4. React Query caches the result and invalidates queries
const createTaskMutation = useMutation({
mutationFn: (data) => orpc.task.create.call(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["tasks"] });
createTaskForm.reset();
},
});
// 5. UI updates automatically with real-time updates
const { data: tasks } = useSuspenseQuery({
queryKey: ["tasks"] as const,
queryFn: () => orpc.task.getAll.call(),
staleTime: 1000,
refetchOnWindowFocus: true,
refetchOnMount: true,
});
// 6. Real-time updates via useTaskUpdates hook
useTaskUpdates((event: RealtimeTaskEvent) => {
switch (event.type) {
case "task-created":
toast.success(`New task created: "${event.task?.text}"! 🎉`);
queryClient.invalidateQueries({ queryKey: ["tasks"] });
break;
case "task-updated":
toast.info(`Task updated: "${event.task?.text}"`);
queryClient.invalidateQueries({ queryKey: ["tasks"] });
break;
}
});
🎯 Part 1 Complete: Core Internal Services Architecture
Congratulations! You've now completed Part 1 of our architecture deep-dive series. In this part, we've covered:
What We've Learned
- ✅ Complete Internal Services Flow: From client component to database
- ✅ oRPC Integration: How TanStack Start and oRPC work together
🚀 What's Next in the Series?
Part 2: Data Flow & State Management
- React Query + oRPC integration patterns
- Real-time updates with Event Publisher
- Caching strategies and optimization
Part 3: Authentication & Security
- Better Auth configuration details
- CSRF protection implementation
- User data isolation patterns
Part 4: External Service Integration
- Plunk email service architecture
- Polar payment webhook processing
- Third-party API patterns
This is Part 1 of the TS Starter Architecture Deep-Dive series. Each part builds on the previous one to give you a complete understanding of the system.