├── src ├── styles │ └── globals.css ├── middleware.ts ├── pages │ ├── sign-in │ │ └── [[...index]].tsx │ ├── sign-up │ │ └── [[...index]].tsx │ ├── _app.tsx │ ├── api │ │ └── trpc │ │ │ └── [trpc].ts │ ├── protected.tsx │ └── index.tsx ├── server │ ├── api │ │ ├── root.ts │ │ ├── routers │ │ │ └── example.ts │ │ └── trpc.ts │ └── db.ts ├── env │ ├── server.mjs │ ├── client.mjs │ └── schema.mjs └── utils │ └── api.ts ├── prisma ├── db.sqlite └── schema.prisma ├── public └── favicon.ico ├── postcss.config.cjs ├── prettier.config.cjs ├── tailwind.config.cjs ├── .github ├── dependabot.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .eslintrc.json ├── .gitignore ├── next.config.mjs ├── tsconfig.json ├── .env.example ├── README.md └── package.json /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /prisma/db.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perkinsjr/t3-app-clerk-minimal/HEAD/prisma/db.sqlite -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perkinsjr/t3-app-clerk-minimal/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | module.exports = { 3 | plugins: [require.resolve("prettier-plugin-tailwindcss")], 4 | }; 5 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { authMiddleware } from "@clerk/nextjs"; 2 | 3 | export default authMiddleware({ 4 | publicRoutes: ["/", "/api/(.*)"] 5 | }); 6 | 7 | export const config = { 8 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"], 9 | }; 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": "./tsconfig.json" 5 | }, 6 | "plugins": ["@typescript-eslint"], 7 | "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended","prettier"], 8 | "rules": { 9 | "@typescript-eslint/consistent-type-imports": "warn" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/sign-in/[[...index]].tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | 3 | const SignInPage = () => ( 4 |
5 | 6 |
7 | ); 8 | 9 | export default SignInPage; 10 | 11 | const styles = { 12 | width: "100vw", 13 | height: "100vh", 14 | display: "flex", 15 | justifyContent: "center", 16 | alignItems: "center", 17 | }; 18 | -------------------------------------------------------------------------------- /src/pages/sign-up/[[...index]].tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | 3 | const SignUpPage = () => ( 4 |
5 | 6 |
7 | ); 8 | 9 | export default SignUpPage; 10 | 11 | const styles = { 12 | width: "100vw", 13 | height: "100vh", 14 | display: "flex", 15 | justifyContent: "center", 16 | alignItems: "center", 17 | }; 18 | -------------------------------------------------------------------------------- /src/server/api/root.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCRouter } from "./trpc"; 2 | import { exampleRouter } from "./routers/example"; 3 | 4 | /** 5 | * This is the primary router for your server. 6 | * 7 | * All routers added in /api/routers should be manually added here 8 | */ 9 | export const appRouter = createTRPCRouter({ 10 | example: exampleRouter, 11 | }); 12 | 13 | // export type definition of API 14 | export type AppRouter = typeof appRouter; 15 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { type AppType } from "next/app"; 2 | import { 3 | ClerkProvider, 4 | } from "@clerk/nextjs"; 5 | import { api } from "../utils/api"; 6 | 7 | import "../styles/globals.css"; 8 | 9 | const MyApp: AppType = ({ Component, pageProps }) => { 10 | 11 | return ( 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default api.withTRPC(MyApp); 19 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "sqlite" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model Example { 14 | id String @id @default(cuid()) 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @updatedAt 17 | } 18 | -------------------------------------------------------------------------------- /src/server/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | import { env } from "../env/server.mjs"; 4 | 5 | const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; 6 | 7 | export const prisma = 8 | globalForPrisma.prisma || 9 | new PrismaClient({ 10 | log: 11 | env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], 12 | }); 13 | 14 | if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; 15 | -------------------------------------------------------------------------------- /src/server/api/routers/example.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; 4 | 5 | export const exampleRouter = createTRPCRouter({ 6 | hello: publicProcedure 7 | .input(z.object({ text: z.string() })) 8 | .query(({ input }) => { 9 | return { 10 | greeting: `Hello ${input.text}`, 11 | }; 12 | }), 13 | getAll: protectedProcedure.query(({ ctx }) => { 14 | return ctx.prisma.example.findMany(); 15 | }), 16 | }); 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** 3 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. 4 | * This is especially useful for Docker builds. 5 | */ 6 | !process.env.SKIP_ENV_VALIDATION && (await import("./src/env/server.mjs")); 7 | 8 | /** @type {import("next").NextConfig} */ 9 | const config = { 10 | reactStrictMode: true, 11 | /* If trying out the experimental appDir, comment the i18n config out 12 | * @see https://github.com/vercel/next.js/issues/41980 */ 13 | i18n: { 14 | locales: ["en"], 15 | defaultLocale: "en", 16 | }, 17 | }; 18 | export default config; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "noUncheckedIndexedAccess": true 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/api/trpc/[trpc].ts: -------------------------------------------------------------------------------- 1 | import { createNextApiHandler } from "@trpc/server/adapters/next"; 2 | 3 | import { env } from "../../../env/server.mjs"; 4 | import { createTRPCContext } from "../../../server/api/trpc"; 5 | import { appRouter } from "../../../server/api/root"; 6 | 7 | // export API handler 8 | export default createNextApiHandler({ 9 | router: appRouter, 10 | createContext: createTRPCContext, 11 | onError: 12 | env.NODE_ENV === "development" 13 | ? ({ path, error }) => { 14 | console.error( 15 | `❌ tRPC failed on ${path ?? ""}: ${error.message}`, 16 | ); 17 | } 18 | : undefined, 19 | }); 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Since the ".env" file is gitignored, you can use the ".env.example" file to 2 | # build a new ".env" file when you clone the repo. Keep this file up-to-date 3 | # when you add new variables to `.env`. 4 | 5 | # This file will be committed to version control, so make sure not to have any 6 | # secrets in it. If you are cloning this repo, create a copy of this file named 7 | # ".env" and populate it with your secrets. 8 | 9 | # When adding additional environment variables, the schema in "/env/schema.mjs" 10 | # should be updated accordingly. 11 | 12 | # Prisma 13 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env 14 | DATABASE_URL="file:./db.sqlite" 15 | 16 | # CLERK Keys 17 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=CLERK_PUBLISHABLE_KEY 18 | CLERK_SECRET_KEY=CLERK_SECRET_KEY -------------------------------------------------------------------------------- /src/pages/protected.tsx: -------------------------------------------------------------------------------- 1 | import { UserButton } from "@clerk/nextjs"; 2 | import { type NextPage } from "next"; 3 | 4 | const Protected: NextPage = () => { 5 | return ( 6 |
7 |
8 |

9 | Welcome to the{" "} 10 | Protected Page 11 |

12 |

Click this User Button!

13 | 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default Protected; 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clerk T3 Minimal 2 | 3 | The following project uses TRPC + Clerk with minimal styling. The project is a good starting point for using TRPC + Clerk. 4 | 5 | ## Project features 6 | 7 | 1. User Context throughout 8 | 2. Protected and public procedures 9 | 3. Sign-in, Sign-Up and User Profile components 10 | 11 | ## How to use 12 | 13 | 1. Create a [Clerk account](https://dashboard.clerk.dev/sign-up) 14 | 2. Copy `.env.example` and rename to `.env` and add your keys found in the dashboard. 15 | 3. Run `npm install` 16 | 4. npx `npx prisma db push` 17 | 4. Run `npm run dev` 18 | 19 | ## Shoutouts 20 | 21 | - Huge shoutout to the team at [TRPC](https://trpc.io) for making an awesome project. 22 | 23 | - Shoutout to [t3 stack](https://create.t3.gg/) for introducing a lot of people to the TRPC and influencing structure of this project. 24 | -------------------------------------------------------------------------------- /src/env/server.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** 3 | * This file is included in `/next.config.mjs` which ensures the app isn't built with invalid env vars. 4 | * It has to be a `.mjs`-file to be imported there. 5 | */ 6 | import { serverSchema, serverEnv } from "./schema.mjs"; 7 | import { env as clientEnv, formatErrors } from "./client.mjs"; 8 | 9 | const _serverEnv = serverSchema.safeParse(serverEnv); 10 | 11 | if (!_serverEnv.success) { 12 | console.error( 13 | "❌ Invalid environment variables:\n", 14 | ...formatErrors(_serverEnv.error.format()), 15 | ); 16 | throw new Error("Invalid environment variables"); 17 | } 18 | 19 | for (let key of Object.keys(_serverEnv.data)) { 20 | if (key.startsWith("NEXT_PUBLIC_")) { 21 | console.warn("❌ You are exposing a server-side env-variable:", key); 22 | 23 | throw new Error("You are exposing a server-side env-variable"); 24 | } 25 | } 26 | 27 | export const env = { ..._serverEnv.data, ...clientEnv }; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/env/client.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { clientEnv, clientSchema } from "./schema.mjs"; 3 | 4 | const _clientEnv = clientSchema.safeParse(clientEnv); 5 | 6 | export const formatErrors = ( 7 | /** @type {import('zod').ZodFormattedError,string>} */ 8 | errors, 9 | ) => 10 | Object.entries(errors) 11 | .map(([name, value]) => { 12 | if (value && "_errors" in value) 13 | return `${name}: ${value._errors.join(", ")}\n`; 14 | }) 15 | .filter(Boolean); 16 | 17 | if (!_clientEnv.success) { 18 | console.error( 19 | "❌ Invalid environment variables:\n", 20 | ...formatErrors(_clientEnv.error.format()), 21 | ); 22 | throw new Error("Invalid environment variables"); 23 | } 24 | 25 | for (let key of Object.keys(_clientEnv.data)) { 26 | if (!key.startsWith("NEXT_PUBLIC_")) { 27 | console.warn( 28 | `❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'`, 29 | ); 30 | 31 | throw new Error("Invalid public environment variable name"); 32 | } 33 | } 34 | 35 | export const env = _clientEnv.data; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clerk-t3-minimal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "next dev", 8 | "postinstall": "prisma generate", 9 | "lint": "next lint", 10 | "start": "next start" 11 | }, 12 | "dependencies": { 13 | "@clerk/nextjs": "^4.19.0", 14 | "@prisma/client": "^4.14.1", 15 | "@tanstack/react-query": "^4.33.0", 16 | "@trpc/client": "^10.27.1", 17 | "@trpc/next": "^10.38.0", 18 | "@trpc/react-query": "^10.16.0", 19 | "@trpc/server": "^10.9.0", 20 | "next": "13.4.4", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "superjson": "1.12.3", 24 | "zod": "^3.20.2" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^20.5.7", 28 | "@types/prettier": "^2.7.2", 29 | "@types/react": "^18.0.35", 30 | "@types/react-dom": "^18.0.10", 31 | "@typescript-eslint/eslint-plugin": "^6.5.0", 32 | "@typescript-eslint/parser": "^5.59.5", 33 | "autoprefixer": "^10.4.7", 34 | "eslint": "^8.38.0", 35 | "eslint-config-next": "13.4.4", 36 | "postcss": "^8.4.28", 37 | "prettier": "^2.8.1", 38 | "prettier-plugin-tailwindcss": "^0.3.0", 39 | "prisma": "^4.14.1", 40 | "tailwindcss": "^3.2.6", 41 | "typescript": "^5.0.2" 42 | }, 43 | "ct3aMetadata": { 44 | "initVersion": "7.4.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/env/schema.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { z } from "zod"; 3 | 4 | /** 5 | * Specify your server-side environment variables schema here. 6 | * This way you can ensure the app isn't built with invalid env vars. 7 | */ 8 | export const serverSchema = z.object({ 9 | DATABASE_URL: z.string().url().optional(), 10 | CLERK_SECRET_KEY: z.string(), 11 | NODE_ENV: z.enum(["development", "test", "production"]), 12 | }); 13 | 14 | /** 15 | * You can't destruct `process.env` as a regular object in the Next.js 16 | * middleware, so you have to do it manually here. 17 | * @type {{ [k in keyof z.input]: string | undefined }} 18 | */ 19 | export const serverEnv = { 20 | DATABASE_URL: process.env.DATABASE_URL, 21 | CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY, 22 | NODE_ENV: process.env.NODE_ENV, 23 | }; 24 | 25 | /** 26 | * Specify your client-side environment variables schema here. 27 | * This way you can ensure the app isn't built with invalid env vars. 28 | * To expose them to the client, prefix them with `NEXT_PUBLIC_`. 29 | */ 30 | export const clientSchema = z.object({ 31 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string(), 32 | }); 33 | 34 | /** 35 | * You can't destruct `process.env` as a regular object, so you have to do 36 | * it manually here. This is because Next.js evaluates this at build time, 37 | * and only used environment variables are included in the build. 38 | * @type {{ [k in keyof z.input]: string | undefined }} 39 | */ 40 | export const clientEnv = { 41 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: 42 | process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, 43 | }; 44 | -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the client-side entrypoint for your tRPC API. 3 | * It's used to create the `api` object which contains the Next.js App-wrapper 4 | * as well as your typesafe react-query hooks. 5 | * 6 | * We also create a few inference helpers for input and output types 7 | */ 8 | import { httpBatchLink, loggerLink } from "@trpc/client"; 9 | import { createTRPCNext } from "@trpc/next"; 10 | import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; 11 | import superjson from "superjson"; 12 | 13 | import { type AppRouter } from "../server/api/root"; 14 | 15 | const getBaseUrl = () => { 16 | if (typeof window !== "undefined") return ""; // browser should use relative url 17 | if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url 18 | return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost 19 | }; 20 | 21 | /** 22 | * A set of typesafe react-query hooks for your tRPC API 23 | */ 24 | export const api = createTRPCNext({ 25 | config() { 26 | return { 27 | /** 28 | * Transformer used for data de-serialization from the server 29 | * @see https://trpc.io/docs/data-transformers 30 | **/ 31 | transformer: superjson, 32 | 33 | /** 34 | * Links used to determine request flow from client to server 35 | * @see https://trpc.io/docs/links 36 | * */ 37 | links: [ 38 | loggerLink({ 39 | enabled: (opts) => 40 | process.env.NODE_ENV === "development" || 41 | (opts.direction === "down" && opts.result instanceof Error), 42 | }), 43 | httpBatchLink({ 44 | url: `${getBaseUrl()}/api/trpc`, 45 | }), 46 | ], 47 | }; 48 | }, 49 | /** 50 | * Whether tRPC should await queries when server rendering pages 51 | * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false 52 | */ 53 | ssr: false, 54 | }); 55 | 56 | /** 57 | * Inference helper for inputs 58 | * @example type HelloInput = RouterInputs['example']['hello'] 59 | **/ 60 | export type RouterInputs = inferRouterInputs; 61 | /** 62 | * Inference helper for outputs 63 | * @example type HelloOutput = RouterOutputs['example']['hello'] 64 | **/ 65 | export type RouterOutputs = inferRouterOutputs; 66 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next"; 2 | import Head from "next/head"; 3 | import Link from "next/link"; 4 | 5 | import { api } from "../utils/api"; 6 | 7 | const Home: NextPage = () => { 8 | const hello = api.example.hello.useQuery({ text: "from tRPC" }); 9 | 10 | return ( 11 | <> 12 | 13 | Create T3 App 14 | 15 | 16 | 17 |
18 |
19 |

20 | Create T3 App 21 |

22 |
23 | 28 |

First Steps →

29 |
30 | Just the basics - Everything you need to know to set up your 31 | database and authentication. 32 |
33 | 34 | 39 |

Documentation →

40 |
41 | Learn more about Create T3 App, the libraries it uses, and how 42 | to deploy it. 43 |
44 | 45 |
46 |

47 | {hello.data ? hello.data.greeting : "Loading tRPC query..."} 48 |

49 | 50 | Check out a protected procedure 51 | 52 |
53 |
54 | 55 | ); 56 | }; 57 | 58 | export default Home; 59 | -------------------------------------------------------------------------------- /src/server/api/trpc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS: 3 | * 1. You want to modify request context (see Part 1) 4 | * 2. You want to create a new middleware or type of procedure (see Part 3) 5 | * 6 | * tl;dr - this is where all the tRPC server stuff is created and plugged in. 7 | * The pieces you will need to use are documented accordingly near the end 8 | */ 9 | 10 | /** 11 | * 1. CONTEXT 12 | * 13 | * This section defines the "contexts" that are available in the backend API 14 | * 15 | * These allow you to access things like the database, the session, etc, when 16 | * processing a request 17 | * 18 | */ 19 | import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; 20 | import { getAuth } from "@clerk/nextjs/server"; 21 | import type { SignedInAuthObject,SignedOutAuthObject } from "@clerk/nextjs/api"; 22 | 23 | import { prisma } from "../db"; 24 | 25 | interface AuthContext { 26 | auth: SignedInAuthObject | SignedOutAuthObject; 27 | } 28 | /** 29 | * This helper generates the "internals" for a tRPC context. If you need to use 30 | * it, you can export it from here 31 | * 32 | * Examples of things you may need it for: 33 | * - testing, so we dont have to mock Next.js' req/res 34 | * - trpc's `createSSGHelpers` where we don't have req/res 35 | * @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts 36 | */ 37 | const createInnerTRPCContext = ({ auth }: AuthContext ) => { 38 | return { 39 | auth, 40 | prisma, 41 | }; 42 | }; 43 | 44 | /** 45 | * This is the actual context you'll use in your router. It will be used to 46 | * process every request that goes through your tRPC endpoint 47 | * @link https://trpc.io/docs/context 48 | */ 49 | export const createTRPCContext = async (opts: CreateNextContextOptions) => { 50 | 51 | 52 | return createInnerTRPCContext({ auth: getAuth(opts.req) }); 53 | }; 54 | 55 | /** 56 | * 2. INITIALIZATION 57 | * 58 | * This is where the trpc api is initialized, connecting the context and 59 | * transformer 60 | */ 61 | import { initTRPC, TRPCError } from "@trpc/server"; 62 | import superjson from "superjson"; 63 | 64 | const t = initTRPC.context().create({ 65 | transformer: superjson, 66 | errorFormatter({ shape }) { 67 | return shape; 68 | }, 69 | }); 70 | 71 | // check if the user is signed in, otherwise through a UNAUTHORIZED CODE 72 | const isAuthed = t.middleware(({ next, ctx }) => { 73 | if (!ctx.auth.userId) { 74 | throw new TRPCError({ code: "UNAUTHORIZED" }); 75 | } 76 | 77 | return next({ 78 | ctx: { 79 | auth: ctx.auth, 80 | }, 81 | }); 82 | }); 83 | /** 84 | * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) 85 | * 86 | * These are the pieces you use to build your tRPC API. You should import these 87 | * a lot in the /src/server/api/routers folder 88 | */ 89 | 90 | /** 91 | * This is how you create new routers and subrouters in your tRPC API 92 | * @see https://trpc.io/docs/router 93 | */ 94 | export const createTRPCRouter = t.router; 95 | 96 | /** 97 | * Public (unauthed) procedure 98 | * 99 | * This is the base piece you use to build new queries and mutations on your 100 | * tRPC API. It does not guarantee that a user querying is authorized, but you 101 | * can still access user session data if they are logged in 102 | */ 103 | export const publicProcedure = t.procedure; 104 | export const protectedProcedure = t.procedure.use(isAuthed); 105 | --------------------------------------------------------------------------------