├── .env.example ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── bun.lockb ├── drizzle.config.ts ├── next.config.js ├── package.json ├── postcss.config.cjs ├── prettier.config.js ├── public └── favicon.ico ├── src ├── app │ ├── _components │ │ ├── create-post.tsx │ │ └── user-button.tsx │ ├── api │ │ └── trpc │ │ │ └── [trpc] │ │ │ └── route.ts │ ├── layout.tsx │ └── page.tsx ├── env.ts ├── middleware.ts ├── server │ ├── api │ │ ├── root.ts │ │ ├── routers │ │ │ └── post.ts │ │ └── trpc.ts │ └── db │ │ ├── index.ts │ │ └── schema.ts ├── styles │ └── globals.css └── trpc │ ├── react.tsx │ └── server.ts ├── tailwind.config.ts └── tsconfig.json /.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 "/src/env.js" 10 | # should be updated accordingly. 11 | 12 | # Drizzle 13 | # Get the Database URL from the "prisma" dropdown selector in PlanetScale. 14 | # Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}" 15 | DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}' 16 | 17 | # I'm using ps-http-sim to simulate a local PlanetScale database. ( `ps-http-sim -mysql-dbname personal -mysql-no-pass` ) 18 | DATABASE_URL='mysql://root:@127.0.0.1:3306/personal' # use this for db:push 19 | DATABASE_URL='http://root:whatever@127.0.0.1:8080' # use this for rest 20 | 21 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_" 22 | CLERK_SECRET_KEY="sk_test_" -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | const config = { 3 | parser: "@typescript-eslint/parser", 4 | parserOptions: { 5 | project: true, 6 | }, 7 | plugins: ["@typescript-eslint"], 8 | extends: [ 9 | "plugin:@next/next/recommended", 10 | "plugin:@typescript-eslint/recommended-type-checked", 11 | "plugin:@typescript-eslint/stylistic-type-checked", 12 | ], 13 | rules: { 14 | // These opinionated rules are enabled in stylistic-type-checked above. 15 | // Feel free to reconfigure them to your own preference. 16 | "@typescript-eslint/array-type": "off", 17 | "@typescript-eslint/consistent-type-definitions": "off", 18 | 19 | "@typescript-eslint/consistent-type-imports": [ 20 | "warn", 21 | { 22 | prefer: "type-imports", 23 | fixStyle: "inline-type-imports", 24 | }, 25 | ], 26 | "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], 27 | "@typescript-eslint/require-await": "off", 28 | "@typescript-eslint/no-misused-promises": [ 29 | "error", 30 | { 31 | checksVoidReturn: { attributes: false }, 32 | }, 33 | ], 34 | }, 35 | }; 36 | 37 | module.exports = config; 38 | -------------------------------------------------------------------------------- /.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 | # database 12 | /prisma/db.sqlite 13 | /prisma/db.sqlite-journal 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | next-env.d.ts 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 35 | .env 36 | .env*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create T3 App 2 | 3 | This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. 4 | 5 | ## What's next? How do I make an app with this? 6 | 7 | We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. 8 | 9 | If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. 10 | 11 | - [Next.js](https://nextjs.org) 12 | - [NextAuth.js](https://next-auth.js.org) 13 | - [Prisma](https://prisma.io) 14 | - [Tailwind CSS](https://tailwindcss.com) 15 | - [tRPC](https://trpc.io) 16 | 17 | ## Learn More 18 | 19 | To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: 20 | 21 | - [Documentation](https://create.t3.gg/) 22 | - [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials 23 | 24 | You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! 25 | 26 | ## How do I deploy this? 27 | 28 | Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. 29 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliusmarminge/t3-appdir-clerk/18aad98096a0b879188e26cdc0be59dc567aa670/bun.lockb -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { type Config } from "drizzle-kit"; 2 | 3 | import { env } from "~/env"; 4 | 5 | export default { 6 | schema: "./src/server/db/schema.ts", 7 | driver: "mysql2", 8 | dbCredentials: { 9 | uri: env.DATABASE_URL, 10 | }, 11 | tablesFilter: ["t3-clerk_*"], 12 | } satisfies Config; 13 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | import createJiti from "jiti"; 2 | import { fileURLToPath } from "node:url"; 3 | const jiti = createJiti(fileURLToPath(import.meta.url)); 4 | 5 | jiti("./src/env"); 6 | 7 | /** @type {import("next").NextConfig} */ 8 | const config = {}; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "t3-clerk", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "next build", 8 | "db:push": "dotenv drizzle-kit push:mysql", 9 | "db:studio": "dotenv drizzle-kit studio", 10 | "dev": "next dev", 11 | "lint": "next lint", 12 | "start": "next start" 13 | }, 14 | "dependencies": { 15 | "@clerk/nextjs": "beta", 16 | "@planetscale/database": "^1.16.0", 17 | "@t3-oss/env-nextjs": "^0.9.2", 18 | "@tanstack/react-query": "^5.22.2", 19 | "@trpc/client": "11.0.0-next-beta.289", 20 | "@trpc/react-query": "11.0.0-next-beta.289", 21 | "@trpc/server": "11.0.0-next-beta.289", 22 | "drizzle-orm": "^0.29.3", 23 | "next": "^14.1.0", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "server-only": "^0.0.1", 27 | "superjson": "^2.2.1", 28 | "zod": "^3.22.4" 29 | }, 30 | "devDependencies": { 31 | "@next/eslint-plugin-next": "^14.1.0", 32 | "@types/eslint": "^8.56.2", 33 | "@types/node": "^20.11.5", 34 | "@types/react": "^18.2.37", 35 | "@types/react-dom": "^18.2.15", 36 | "@typescript-eslint/eslint-plugin": "^7.0.2", 37 | "@typescript-eslint/parser": "^7.0.2", 38 | "drizzle-kit": "^0.20.14", 39 | "eslint": "^8.56.0", 40 | "jiti": "^1.21.0", 41 | "mysql2": "^3.9.1", 42 | "postcss": "^8.4.35", 43 | "prettier": "^3.2.5", 44 | "prettier-plugin-tailwindcss": "^0.5.11", 45 | "tailwindcss": "^3.4.1", 46 | "typescript": "^5.3.3" 47 | }, 48 | "ct3aMetadata": { 49 | "initVersion": "7.24.2" 50 | }, 51 | "packageManager": "bun@1.0.25" 52 | } 53 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | }, 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */ 2 | const config = { 3 | plugins: ["prettier-plugin-tailwindcss"], 4 | }; 5 | 6 | export default config; 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juliusmarminge/t3-appdir-clerk/18aad98096a0b879188e26cdc0be59dc567aa670/public/favicon.ico -------------------------------------------------------------------------------- /src/app/_components/create-post.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | import { useState } from "react"; 5 | 6 | import { api } from "~/trpc/react"; 7 | 8 | export function CreatePost() { 9 | const router = useRouter(); 10 | const [name, setName] = useState(""); 11 | 12 | const createPost = api.post.create.useMutation({ 13 | onSuccess: () => { 14 | router.refresh(); 15 | setName(""); 16 | }, 17 | }); 18 | 19 | return ( 20 |
{ 22 | e.preventDefault(); 23 | createPost.mutate({ name }); 24 | }} 25 | className="flex flex-col gap-2" 26 | > 27 | setName(e.target.value)} 32 | className="w-full rounded-full px-4 py-2 text-black" 33 | /> 34 | 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/app/_components/user-button.tsx: -------------------------------------------------------------------------------- 1 | import { SignInButton, SignedIn, SignedOut, UserButton } from "@clerk/nextjs"; 2 | 3 | export function OurUserButton() { 4 | return ( 5 | <> 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/api/trpc/[trpc]/route.ts: -------------------------------------------------------------------------------- 1 | import { getAuth } from "@clerk/nextjs/server"; 2 | import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; 3 | import { type NextRequest } from "next/server"; 4 | 5 | import { env } from "~/env"; 6 | import { appRouter } from "~/server/api/root"; 7 | import { createTRPCContext } from "~/server/api/trpc"; 8 | 9 | /** 10 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when 11 | * handling a HTTP request (e.g. when you make requests from Client Components). 12 | */ 13 | const createContext = async (req: NextRequest) => { 14 | return createTRPCContext({ 15 | auth: getAuth(req), 16 | }); 17 | }; 18 | 19 | const handler = (req: NextRequest) => 20 | fetchRequestHandler({ 21 | endpoint: "/api/trpc", 22 | req, 23 | router: appRouter, 24 | createContext: () => createContext(req), 25 | onError: 26 | env.NODE_ENV === "development" 27 | ? ({ path, error }) => { 28 | console.error( 29 | `❌ tRPC failed on ${path ?? ""}: ${error.message}`, 30 | ); 31 | } 32 | : undefined, 33 | }); 34 | 35 | export { handler as GET, handler as POST }; 36 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "~/styles/globals.css"; 2 | 3 | import { ClerkProvider } from "@clerk/nextjs"; 4 | import { Inter } from "next/font/google"; 5 | 6 | import { TRPCReactProvider } from "~/trpc/react"; 7 | 8 | const inter = Inter({ 9 | subsets: ["latin"], 10 | variable: "--font-sans", 11 | }); 12 | 13 | export const metadata = { 14 | title: "Create T3 App", 15 | description: "Generated by create-t3-app", 16 | icons: [{ rel: "icon", url: "/favicon.ico" }], 17 | }; 18 | 19 | export default function RootLayout({ 20 | children, 21 | }: { 22 | children: React.ReactNode; 23 | }) { 24 | return ( 25 | 26 | 27 | 28 | {children} 29 | 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignedIn } from "@clerk/nextjs"; 2 | import Link from "next/link"; 3 | 4 | import { CreatePost } from "~/app/_components/create-post"; 5 | import { api } from "~/trpc/server"; 6 | import { OurUserButton } from "./_components/user-button"; 7 | 8 | export default async function Home() { 9 | const hello = await api.post.hello({ text: "from tRPC" }); 10 | 11 | return ( 12 |
13 |
14 | 15 |
16 | 17 |
18 |

19 | Create T3 App 20 |

21 |
22 | 27 |

First Steps →

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

Documentation →

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

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

49 |
50 | 51 | 52 | 53 | 54 |
55 |
56 | ); 57 | } 58 | 59 | async function CrudShowcase() { 60 | const latestPost = await api.post.getLatest(); 61 | 62 | return ( 63 |
64 | {latestPost ? ( 65 |

Your most recent post: {latestPost.name}

66 | ) : ( 67 |

You have no posts yet.

68 | )} 69 | 70 | 71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/env.ts: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-nextjs"; 2 | import { z } from "zod"; 3 | 4 | export const env = createEnv({ 5 | /** 6 | * Specify your server-side environment variables schema here. This way you can ensure the app 7 | * isn't built with invalid env vars. 8 | */ 9 | server: { 10 | DATABASE_URL: z 11 | .string() 12 | .url() 13 | .refine( 14 | (str) => !str.includes("YOUR_MYSQL_URL_HERE"), 15 | "You forgot to change the default URL", 16 | ), 17 | NODE_ENV: z 18 | .enum(["development", "test", "production"]) 19 | .default("development"), 20 | CLERK_SECRET_KEY: z.string().refine((s) => s.startsWith("sk_")), 21 | }, 22 | 23 | /** 24 | * Specify your client-side environment variables schema here. This way you can ensure the app 25 | * isn't built with invalid env vars. To expose them to the client, prefix them with 26 | * `NEXT_PUBLIC_`. 27 | */ 28 | client: { 29 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z 30 | .string() 31 | .refine((s) => s.startsWith("pk_")), 32 | }, 33 | 34 | /** 35 | * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. 36 | * middlewares) or client-side so we need to destruct manually. 37 | */ 38 | experimental__runtimeEnv: { 39 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: 40 | process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, 41 | }, 42 | /** 43 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially 44 | * useful for Docker builds. 45 | */ 46 | skipValidation: !!process.env.SKIP_ENV_VALIDATION, 47 | /** 48 | * Makes it so that empty strings are treated as undefined. 49 | * `SOME_VAR: z.string()` and `SOME_VAR=''` will throw an error. 50 | */ 51 | emptyStringAsUndefined: true, 52 | }); 53 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; 2 | import { NextResponse } from "next/server"; 3 | 4 | const isPublicRoute = createRouteMatcher(["/"]); 5 | const isApiRoute = createRouteMatcher(["/api/(.*)"]); 6 | 7 | export default clerkMiddleware((auth, req) => { 8 | if (isApiRoute(req)) return NextResponse.next(); 9 | 10 | const { userId, redirectToSignIn, protect } = auth(); 11 | const isPublic = isPublicRoute(req); 12 | 13 | if (!userId && !isPublic) redirectToSignIn(); 14 | if (!isPublic) protect(); 15 | 16 | return NextResponse.next(); 17 | }); 18 | 19 | export const config = { 20 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/api(.*)"], 21 | }; 22 | -------------------------------------------------------------------------------- /src/server/api/root.ts: -------------------------------------------------------------------------------- 1 | import { postRouter } from "~/server/api/routers/post"; 2 | import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc"; 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 | post: postRouter, 11 | }); 12 | 13 | // export type definition of API 14 | export type AppRouter = typeof appRouter; 15 | 16 | /** 17 | * Create a server-side caller for the tRPC API 18 | * @example 19 | * const trpc = createCaller(createContext); 20 | * const res = await trpc.post.all(); 21 | * ^? Post[] 22 | */ 23 | export const createCaller = createCallerFactory(appRouter); 24 | -------------------------------------------------------------------------------- /src/server/api/routers/post.ts: -------------------------------------------------------------------------------- 1 | import { currentUser } from "@clerk/nextjs/server"; 2 | import { z } from "zod"; 3 | 4 | import { 5 | createTRPCRouter, 6 | protectedProcedure, 7 | publicProcedure, 8 | } from "~/server/api/trpc"; 9 | import { posts } from "~/server/db/schema"; 10 | 11 | export const postRouter = createTRPCRouter({ 12 | hello: publicProcedure 13 | .input(z.object({ text: z.string() })) 14 | .query(({ input }) => { 15 | return { 16 | greeting: `Hello ${input.text}`, 17 | }; 18 | }), 19 | 20 | create: protectedProcedure 21 | .input(z.object({ name: z.string().min(1) })) 22 | .mutation(async ({ ctx, input }) => { 23 | // simulate a slow db call 24 | await new Promise((resolve) => setTimeout(resolve, 1000)); 25 | 26 | await ctx.db.insert(posts).values({ 27 | name: input.name, 28 | }); 29 | }), 30 | 31 | getLatest: protectedProcedure.query(async ({ ctx }) => { 32 | const user = await currentUser(); 33 | console.log("Context", ctx.auth, user); 34 | 35 | return ctx.db.query.posts.findFirst({ 36 | orderBy: (posts, { desc }) => [desc(posts.createdAt)], 37 | }); 38 | }), 39 | }); 40 | -------------------------------------------------------------------------------- /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. The pieces you will 7 | * need to use are documented accordingly near the end. 8 | */ 9 | import { TRPCError, initTRPC } from "@trpc/server"; 10 | import superjson from "superjson"; 11 | import { ZodError } from "zod"; 12 | import type { getAuth } from "@clerk/nextjs/server"; 13 | 14 | import { db } from "~/server/db"; 15 | 16 | /** 17 | * 1. CONTEXT 18 | * 19 | * This section defines the "contexts" that are available in the backend API. 20 | * 21 | * These allow you to access things when processing a request, like the database, the session, etc. 22 | * 23 | * This helper generates the "internals" for a tRPC context. The API handler and RSC clients each 24 | * wrap this and provides the required context. 25 | * 26 | * @see https://trpc.io/docs/server/context 27 | */ 28 | 29 | export const createTRPCContext = async (opts: { 30 | auth: ReturnType; 31 | }) => { 32 | return { 33 | db, 34 | ...opts, 35 | }; 36 | }; 37 | 38 | /** 39 | * 2. INITIALIZATION 40 | * 41 | * This is where the tRPC API is initialized, connecting the context and transformer. We also parse 42 | * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation 43 | * errors on the backend. 44 | */ 45 | const t = initTRPC.context().create({ 46 | transformer: superjson, 47 | errorFormatter({ shape, error }) { 48 | return { 49 | ...shape, 50 | data: { 51 | ...shape.data, 52 | zodError: 53 | error.cause instanceof ZodError ? error.cause.flatten() : null, 54 | }, 55 | }; 56 | }, 57 | }); 58 | 59 | /** 60 | * Create a server-side caller 61 | * @see https://trpc.io/docs/server/server-side-calls 62 | */ 63 | export const createCallerFactory = t.createCallerFactory; 64 | 65 | /** 66 | * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) 67 | * 68 | * These are the pieces you use to build your tRPC API. You should import these a lot in the 69 | * "/src/server/api/routers" directory. 70 | */ 71 | 72 | /** 73 | * This is how you create new routers and sub-routers in your tRPC API. 74 | * 75 | * @see https://trpc.io/docs/router 76 | */ 77 | export const createTRPCRouter = t.router; 78 | 79 | /** 80 | * Public (unauthenticated) procedure 81 | * 82 | * This is the base piece you use to build new queries and mutations on your tRPC API. It does not 83 | * guarantee that a user querying is authorized, but you can still access user session data if they 84 | * are logged in. 85 | */ 86 | export const publicProcedure = t.procedure; 87 | 88 | /** 89 | * Reusable middleware that enforces users are logged in before running the 90 | * procedure 91 | */ 92 | const enforceUserIsAuthed = t.middleware(({ next, ctx }) => { 93 | if (!ctx.auth.userId) { 94 | throw new TRPCError({ code: "UNAUTHORIZED" }); 95 | } 96 | return next({ 97 | ctx: { 98 | auth: ctx.auth, 99 | }, 100 | }); 101 | }); 102 | 103 | /** 104 | * Protected (authed) procedure 105 | * 106 | * If you want a query or mutation to ONLY be accessible to logged in users, use 107 | * this. It verifies the session is valid and guarantees ctx.session.user is not 108 | * null 109 | * 110 | * @see https://trpc.io/docs/procedures 111 | */ 112 | export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); 113 | -------------------------------------------------------------------------------- /src/server/db/index.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "@planetscale/database"; 2 | import { drizzle } from "drizzle-orm/planetscale-serverless"; 3 | 4 | import { env } from "~/env"; 5 | import * as schema from "./schema"; 6 | 7 | export const db = drizzle( 8 | new Client({ 9 | url: env.DATABASE_URL, 10 | }).connection(), 11 | { schema }, 12 | ); 13 | -------------------------------------------------------------------------------- /src/server/db/schema.ts: -------------------------------------------------------------------------------- 1 | // Example model schema from the Drizzle docs 2 | // https://orm.drizzle.team/docs/sql-schema-declaration 3 | 4 | import { sql } from "drizzle-orm"; 5 | import { 6 | bigint, 7 | index, 8 | mysqlTableCreator, 9 | timestamp, 10 | varchar, 11 | } from "drizzle-orm/mysql-core"; 12 | 13 | /** 14 | * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same 15 | * database instance for multiple projects. 16 | * 17 | * @see https://orm.drizzle.team/docs/goodies#multi-project-schema 18 | */ 19 | export const mysqlTable = mysqlTableCreator((name) => `t3-clerk_${name}`); 20 | 21 | export const posts = mysqlTable( 22 | "post", 23 | { 24 | id: bigint("id", { mode: "number" }).primaryKey().autoincrement(), 25 | name: varchar("name", { length: 256 }), 26 | createdAt: timestamp("created_at") 27 | .default(sql`CURRENT_TIMESTAMP`) 28 | .notNull(), 29 | updatedAt: timestamp("updatedAt").onUpdateNow(), 30 | }, 31 | (example) => ({ 32 | nameIndex: index("name_idx").on(example.name), 33 | }), 34 | ); 35 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/trpc/react.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 5 | import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client"; 6 | import { createTRPCReact } from "@trpc/react-query"; 7 | import SuperJSON from "superjson"; 8 | 9 | import type { AppRouter } from "~/server/api/root"; 10 | 11 | export const api = createTRPCReact(); 12 | 13 | export function TRPCReactProvider(props: { children: React.ReactNode }) { 14 | const [queryClient] = useState(() => new QueryClient()); 15 | 16 | const [trpcClient] = useState(() => 17 | api.createClient({ 18 | links: [ 19 | loggerLink({ 20 | enabled: (op) => 21 | process.env.NODE_ENV === "development" || 22 | (op.direction === "down" && op.result instanceof Error), 23 | }), 24 | unstable_httpBatchStreamLink({ 25 | transformer: SuperJSON, 26 | url: getBaseUrl() + "/api/trpc", 27 | async headers() { 28 | const headers = new Headers(); 29 | headers.set("x-trpc-source", "nextjs-react"); 30 | return headers; 31 | }, 32 | }), 33 | ], 34 | }), 35 | ); 36 | 37 | return ( 38 | 39 | 40 | {props.children} 41 | 42 | 43 | ); 44 | } 45 | 46 | function getBaseUrl() { 47 | if (typeof window !== "undefined") return window.location.origin; 48 | if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; 49 | return `http://localhost:${process.env.PORT ?? 3000}`; 50 | } 51 | -------------------------------------------------------------------------------- /src/trpc/server.ts: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | 3 | import { auth } from "@clerk/nextjs/server"; 4 | import { cache } from "react"; 5 | 6 | import { createCaller } from "~/server/api/root"; 7 | import { createTRPCContext } from "~/server/api/trpc"; 8 | 9 | /** 10 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when 11 | * handling a tRPC call from a React Server Component. 12 | */ 13 | const createContext = cache(() => { 14 | return createTRPCContext({ 15 | auth: auth(), 16 | }); 17 | }); 18 | 19 | export const api = createCaller(createContext); 20 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { type Config } from "tailwindcss"; 2 | import { fontFamily } from "tailwindcss/defaultTheme"; 3 | 4 | export default { 5 | content: ["./src/**/*.tsx"], 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | sans: ["var(--font-sans)", ...fontFamily.sans], 10 | }, 11 | }, 12 | }, 13 | plugins: [], 14 | } satisfies Config; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Base Options: */ 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "target": "es2022", 7 | "allowJs": true, 8 | "resolveJsonModule": true, 9 | "moduleDetection": "force", 10 | "isolatedModules": true, 11 | 12 | /* Strictness */ 13 | "strict": true, 14 | "noUncheckedIndexedAccess": true, 15 | "checkJs": true, 16 | 17 | /* Bundled projects */ 18 | "lib": ["dom", "dom.iterable", "ES2022"], 19 | "noEmit": true, 20 | "module": "ESNext", 21 | "moduleResolution": "Bundler", 22 | "jsx": "preserve", 23 | "plugins": [{ "name": "next" }], 24 | "incremental": true, 25 | 26 | /* Path Aliases */ 27 | "baseUrl": ".", 28 | "paths": { 29 | "~/*": ["./src/*"], 30 | }, 31 | }, 32 | "include": [ 33 | ".eslintrc.cjs", 34 | "next-env.d.ts", 35 | "**/*.ts", 36 | "**/*.tsx", 37 | "**/*.cjs", 38 | "**/*.js", 39 | ".next/types/**/*.ts", 40 | ], 41 | "exclude": ["node_modules"], 42 | } 43 | --------------------------------------------------------------------------------