diff options
| author | Fuwn <[email protected]> | 2026-02-03 19:56:19 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-03 19:56:37 -0800 |
| commit | 6be34a75cd060444734080ac14d3a33eea630f15 (patch) | |
| tree | beda873457cf5d6f73e97391c902acf8a9b69712 /packages/web/src/server/api | |
| parent | feat(iku): Support comment-aware whitespace checking (diff) | |
| download | archived-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.ts | 23 | ||||
| -rw-r--r-- | packages/web/src/server/api/routers/post.ts | 38 | ||||
| -rw-r--r-- | packages/web/src/server/api/trpc.ts | 134 |
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 }, + }, + }); + }); |