aboutsummaryrefslogtreecommitdiff
path: root/packages/web/src/server/api
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-03 19:56:19 -0800
committerFuwn <[email protected]>2026-02-03 19:56:37 -0800
commit6be34a75cd060444734080ac14d3a33eea630f15 (patch)
treebeda873457cf5d6f73e97391c902acf8a9b69712 /packages/web/src/server/api
parentfeat(iku): Support comment-aware whitespace checking (diff)
downloadarchived-imemio-6be34a75cd060444734080ac14d3a33eea630f15.tar.xz
archived-imemio-6be34a75cd060444734080ac14d3a33eea630f15.zip
feat(web): Initialise T3 Stack
Diffstat (limited to 'packages/web/src/server/api')
-rw-r--r--packages/web/src/server/api/root.ts23
-rw-r--r--packages/web/src/server/api/routers/post.ts38
-rw-r--r--packages/web/src/server/api/trpc.ts134
3 files changed, 195 insertions, 0 deletions
diff --git a/packages/web/src/server/api/root.ts b/packages/web/src/server/api/root.ts
new file mode 100644
index 0000000..374285c
--- /dev/null
+++ b/packages/web/src/server/api/root.ts
@@ -0,0 +1,23 @@
+import { postRouter } from "~/server/api/routers/post";
+import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc";
+
+/**
+ * This is the primary router for your server.
+ *
+ * All routers added in /api/routers should be manually added here.
+ */
+export const appRouter = createTRPCRouter({
+ post: postRouter,
+});
+
+// export type definition of API
+export type AppRouter = typeof appRouter;
+
+/**
+ * Create a server-side caller for the tRPC API.
+ * @example
+ * const trpc = createCaller(createContext);
+ * const res = await trpc.post.all();
+ * ^? Post[]
+ */
+export const createCaller = createCallerFactory(appRouter);
diff --git a/packages/web/src/server/api/routers/post.ts b/packages/web/src/server/api/routers/post.ts
new file mode 100644
index 0000000..2bd03a4
--- /dev/null
+++ b/packages/web/src/server/api/routers/post.ts
@@ -0,0 +1,38 @@
+import { z } from "zod";
+import {
+ createTRPCRouter,
+ protectedProcedure,
+ publicProcedure,
+} from "~/server/api/trpc";
+import { posts } from "~/server/db/schema";
+
+export const postRouter = createTRPCRouter({
+ hello: publicProcedure
+ .input(z.object({ text: z.string() }))
+ .query(({ input }) => {
+ return {
+ greeting: `Hello ${input.text}`,
+ };
+ }),
+
+ create: protectedProcedure
+ .input(z.object({ name: z.string().min(1) }))
+ .mutation(async ({ ctx, input }) => {
+ await ctx.db.insert(posts).values({
+ name: input.name,
+ createdById: ctx.session.user.id,
+ });
+ }),
+
+ getLatest: protectedProcedure.query(async ({ ctx }) => {
+ const post = await ctx.db.query.posts.findFirst({
+ orderBy: (posts, { desc }) => [desc(posts.createdAt)],
+ });
+
+ return post ?? null;
+ }),
+
+ getSecretMessage: protectedProcedure.query(() => {
+ return "you can now see this secret message!";
+ }),
+});
diff --git a/packages/web/src/server/api/trpc.ts b/packages/web/src/server/api/trpc.ts
new file mode 100644
index 0000000..65c86f4
--- /dev/null
+++ b/packages/web/src/server/api/trpc.ts
@@ -0,0 +1,134 @@
+/**
+ * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
+ * 1. You want to modify request context (see Part 1).
+ * 2. You want to create a new middleware or type of procedure (see Part 3).
+ *
+ * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
+ * need to use are documented accordingly near the end.
+ */
+
+import { initTRPC, TRPCError } from "@trpc/server";
+import superjson from "superjson";
+import { ZodError } from "zod";
+import { auth } from "~/server/auth";
+import { db } from "~/server/db";
+
+/**
+ * 1. CONTEXT
+ *
+ * This section defines the "contexts" that are available in the backend API.
+ *
+ * These allow you to access things when processing a request, like the database, the session, etc.
+ *
+ * This helper generates the "internals" for a tRPC context. The API handler and RSC clients each
+ * wrap this and provides the required context.
+ *
+ * @see https://trpc.io/docs/server/context
+ */
+export const createTRPCContext = async (opts: { headers: Headers }) => {
+ const session = await auth();
+
+ return {
+ db,
+ session,
+ ...opts,
+ };
+};
+
+/**
+ * 2. INITIALIZATION
+ *
+ * This is where the tRPC API is initialized, connecting the context and transformer. We also parse
+ * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
+ * errors on the backend.
+ */
+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,
+ },
+ };
+ },
+});
+
+/**
+ * Create a server-side caller.
+ *
+ * @see https://trpc.io/docs/server/server-side-calls
+ */
+export const createCallerFactory = t.createCallerFactory;
+
+/**
+ * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
+ *
+ * These are the pieces you use to build your tRPC API. You should import these a lot in the
+ * "/src/server/api/routers" directory.
+ */
+
+/**
+ * This is how you create new routers and sub-routers in your tRPC API.
+ *
+ * @see https://trpc.io/docs/router
+ */
+export const createTRPCRouter = t.router;
+
+/**
+ * Middleware for timing procedure execution and adding an artificial delay in development.
+ *
+ * You can remove this if you don't like it, but it can help catch unwanted waterfalls by simulating
+ * network latency that would occur in production but not in local development.
+ */
+const timingMiddleware = t.middleware(async ({ next, path }) => {
+ const start = Date.now();
+
+ if (t._config.isDev) {
+ // artificial delay in dev
+ const waitMs = Math.floor(Math.random() * 400) + 100;
+
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
+ }
+
+ const result = await next();
+ const end = Date.now();
+
+ console.log(`[TRPC] ${path} took ${end - start}ms to execute`);
+
+ return result;
+});
+
+/**
+ * Public (unauthenticated) procedure
+ *
+ * This is the base piece you use to build new queries and mutations on your tRPC API. It does not
+ * guarantee that a user querying is authorized, but you can still access user session data if they
+ * are logged in.
+ */
+export const publicProcedure = t.procedure.use(timingMiddleware);
+
+/**
+ * Protected (authenticated) procedure
+ *
+ * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
+ * the session is valid and guarantees `ctx.session.user` is not null.
+ *
+ * @see https://trpc.io/docs/procedures
+ */
+export const protectedProcedure = t.procedure
+ .use(timingMiddleware)
+ .use(({ ctx, next }) => {
+ if (!ctx.session?.user) {
+ throw new TRPCError({ code: "UNAUTHORIZED" });
+ }
+
+ return next({
+ ctx: {
+ // infers the `session` as non-nullable
+ session: { ...ctx.session, user: ctx.session.user },
+ },
+ });
+ });