tRPC

This is how your API handler would typically look like:

src/main.ts
import express from "express";
import * as fs from "node:fs/promises";
import { z } from "zod";
import { initTRPC } from "@trpc/server";
import { createExpressMiddleware } from "@trpc/server/adapters/express";
import type { Request } from "express";
import { bootstrapAuth, getUser } from "./auth"; // See below

function startExpressTrpcServer() {
    bootstrapAuth({
        implementation: "real", // or "mock", see: https://docs.oidc-spa.dev/v/v8/integration-guides/backend-token-validation/mock-modes
        issuerUri: process.env.OIDC_ISSUER_URI!,
        expectedAudience: process.env.OIDC_AUDIENCE
    });

    const app = express();

    /**
     * Key idea: Wether you use use Express or something else
     * as underlying HTTP framework, just expose whatever the 
     * request object representation is to the global context.
     * oidc-spa will be able to extract the auth context from it.
     */
    const createContext = ({ req }: { req: Request }) => ({ req });

    type Context = ReturnType<typeof createContext>;

    const t = initTRPC.context<Context>().create();

    const appRouter = t.router({
        todos: t.procedure.query(async ({ ctx }) => {
            const user = await getUser({ req: ctx.req });
            const json = await fs.readFile(
                `todos_${user.id}.json`, 
                "utf8"
            );
            return JSON.parse(json);
        }),

        todosForSupportStaff: t.procedure
            .input(z.object({ userId: z.string() }))
            .query(async ({ ctx, input }) => {
                // Will reject the request if user making the request
                // doesn't have "support-staff" role
                await getUser({ req: ctx.req, requiredRole: "support-staff" });
                const json = await fs.readFile(`todos_${input.userId}.json`, "utf8");
                return JSON.parse(json);
            })
    });

    app.use(
        "/trpc",
        // Needed for tRPC POST requests.
        express.json(),
        createExpressMiddleware({
            router: appRouter,
            createContext
        })
    );

    app.listen(parseInt(process.env.PORT ?? "3000"), () => {
        console.log("Server running");
    });
}

Let's see how to export the utils to make it happen:

Last updated

Was this helpful?