├── bun.lockb ├── public └── favicon.ico ├── src ├── styles │ └── globals.css ├── pages │ ├── api │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ └── trpc │ │ │ └── [trpc].ts │ ├── _app.tsx │ └── index.tsx ├── server │ ├── db.ts │ ├── api │ │ ├── root.ts │ │ ├── routers │ │ │ └── post.ts │ │ └── trpc.ts │ └── auth.ts ├── env.js └── utils │ └── api.ts ├── postcss.config.cjs ├── prettier.config.js ├── tailwind.config.ts ├── docker-compose.yml ├── next.config.js ├── .gitignore ├── .env.example ├── tsconfig.json ├── .eslintrc.cjs ├── package.json ├── README.md ├── start-database.sh └── prisma └── schema.prisma /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodevoid/v6-academy/HEAD/bun.lockb -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodevoid/v6-academy/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | }, 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /src/pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | 3 | import { authOptions } from "~/server/auth"; 4 | 5 | export default NextAuth(authOptions); 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | postgres: 5 | image: postgres 6 | container_name: v6-academy 7 | ports: 8 | - "5432:5432" 9 | environment: 10 | POSTGRES_USER: user 11 | POSTGRES_PASSWORD: password 12 | POSTGRES_DB: v6-academy 13 | volumes: 14 | - data_postgres:/var/lib/postgresql/data 15 | 16 | volumes: 17 | data_postgres: 18 | -------------------------------------------------------------------------------- /src/server/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | import { env } from "~/env"; 4 | 5 | const createPrismaClient = () => 6 | new PrismaClient({ 7 | log: 8 | env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], 9 | }); 10 | 11 | const globalForPrisma = globalThis as unknown as { 12 | prisma: ReturnType | undefined; 13 | }; 14 | 15 | export const db = globalForPrisma.prisma ?? createPrismaClient(); 16 | 17 | if (env.NODE_ENV !== "production") globalForPrisma.prisma = db; 18 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful 3 | * for Docker builds. 4 | */ 5 | await import("./src/env.js"); 6 | 7 | /** @type {import("next").NextConfig} */ 8 | const config = { 9 | reactStrictMode: true, 10 | 11 | /** 12 | * If you are using `appDir` then you must comment the below `i18n` config out. 13 | * 14 | * @see https://github.com/vercel/next.js/issues/41980 15 | */ 16 | i18n: { 17 | locales: ["en"], 18 | defaultLocale: "en", 19 | }, 20 | }; 21 | 22 | export default config; 23 | -------------------------------------------------------------------------------- /src/pages/api/trpc/[trpc].ts: -------------------------------------------------------------------------------- 1 | import { createNextApiHandler } from "@trpc/server/adapters/next"; 2 | 3 | import { env } from "~/env"; 4 | import { appRouter } from "~/server/api/root"; 5 | import { createTRPCContext } from "~/server/api/trpc"; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { type Session } from "next-auth"; 2 | import { SessionProvider } from "next-auth/react"; 3 | import { type AppType } from "next/app"; 4 | import { Inter } from "next/font/google"; 5 | 6 | import { api } from "~/utils/api"; 7 | 8 | import "~/styles/globals.css"; 9 | 10 | const inter = Inter({ 11 | subsets: ["latin"], 12 | variable: "--font-sans", 13 | }); 14 | 15 | const MyApp: AppType<{ session: Session | null }> = ({ 16 | Component, 17 | pageProps: { session, ...pageProps }, 18 | }) => { 19 | return ( 20 | 21 |
22 | 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default api.withTRPC(MyApp); 29 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Prisma 2 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env 3 | DATABASE_URL="file:./db.sqlite" 4 | 5 | # Metadata 6 | NEXT_PUBLIC_BASE_URL="http://localhost:3000" 7 | NEXT_PUBLIC_OG_IMAGE_URL="https://utfs.io/f/9bad171b-6dc0-4bc3-9972-585806e31c66-k3an9c.jpg" 8 | 9 | # Next Auth 10 | # You can generate a new secret on the command line with: 11 | # openssl rand -base64 32 12 | # https://next-auth.js.org/configuration/options#secret 13 | # NEXTAUTH_SECRET="" 14 | NEXTAUTH_URL="http://localhost:3000" 15 | 16 | # Next Auth Discord Provider 17 | DISCORD_CLIENT_ID="" 18 | DISCORD_CLIENT_SECRET="" 19 | 20 | # Next Auth Github Provider 21 | GITHUB_CLIENT_ID="" 22 | GITHUB_CLIENT_SECRET="" 23 | 24 | # Next Auth Google Provider 25 | GOOGLE_CLIENT_ID="" 26 | GOOGLE_CLIENT_SECRET="" 27 | 28 | # Uploadthing 29 | UPLOADTHING_SECRET="" 30 | UPLOADTHING_APP_ID="" -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | const config = { 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "project": true 6 | }, 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "extends": [ 11 | "next/core-web-vitals", 12 | "plugin:@typescript-eslint/recommended-type-checked", 13 | "plugin:@typescript-eslint/stylistic-type-checked" 14 | ], 15 | "rules": { 16 | "@typescript-eslint/array-type": "off", 17 | "@typescript-eslint/consistent-type-definitions": "off", 18 | "@typescript-eslint/consistent-type-imports": [ 19 | "warn", 20 | { 21 | "prefer": "type-imports", 22 | "fixStyle": "inline-type-imports" 23 | } 24 | ], 25 | "@typescript-eslint/no-unused-vars": [ 26 | "warn", 27 | { 28 | "argsIgnorePattern": "^_" 29 | } 30 | ], 31 | "@typescript-eslint/require-await": "off", 32 | "@typescript-eslint/no-misused-promises": [ 33 | "error", 34 | { 35 | "checksVoidReturn": { 36 | "attributes": false 37 | } 38 | } 39 | ] 40 | } 41 | } 42 | module.exports = config; -------------------------------------------------------------------------------- /src/server/api/routers/post.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | import { 4 | createTRPCRouter, 5 | protectedProcedure, 6 | publicProcedure, 7 | } from "~/server/api/trpc"; 8 | 9 | export const postRouter = createTRPCRouter({ 10 | hello: publicProcedure 11 | .input(z.object({ text: z.string() })) 12 | .query(({ input }) => { 13 | return { 14 | greeting: `Hello ${input.text}`, 15 | }; 16 | }), 17 | 18 | create: protectedProcedure 19 | .input(z.object({ name: z.string().min(1) })) 20 | .mutation(async ({ ctx, input }) => { 21 | // simulate a slow db call 22 | await new Promise((resolve) => setTimeout(resolve, 1000)); 23 | 24 | return ctx.db.post.create({ 25 | data: { 26 | name: input.name, 27 | createdBy: { connect: { id: ctx.session.user.id } }, 28 | }, 29 | }); 30 | }), 31 | 32 | getLatest: protectedProcedure.query(({ ctx }) => { 33 | return ctx.db.post.findFirst({ 34 | orderBy: { createdAt: "desc" }, 35 | where: { createdBy: { id: ctx.session.user.id } }, 36 | }); 37 | }), 38 | 39 | getSecretMessage: protectedProcedure.query(() => { 40 | return "you can now see this secret message!"; 41 | }), 42 | }); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v6-academy", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "next build", 8 | "db:push": "prisma db push", 9 | "db:studio": "prisma studio", 10 | "dev": "next dev", 11 | "postinstall": "prisma generate", 12 | "lint": "next lint", 13 | "start": "next start" 14 | }, 15 | "dependencies": { 16 | "@auth/prisma-adapter": "^1.4.0", 17 | "@prisma/client": "^5.10.2", 18 | "@t3-oss/env-nextjs": "^0.9.2", 19 | "@tanstack/react-query": "^5.25.0", 20 | "@trpc/client": "next", 21 | "@trpc/next": "next", 22 | "@trpc/react-query": "next", 23 | "@trpc/server": "next", 24 | "next": "^14.2.1", 25 | "next-auth": "^4.24.6", 26 | "react": "18.2.0", 27 | "react-dom": "18.2.0", 28 | "superjson": "^2.2.1", 29 | "zod": "^3.22.4" 30 | }, 31 | "devDependencies": { 32 | "@types/eslint": "^8.56.2", 33 | "@types/node": "^20.11.20", 34 | "@types/react": "^18.2.57", 35 | "@types/react-dom": "^18.2.19", 36 | "@typescript-eslint/eslint-plugin": "^7.1.1", 37 | "@typescript-eslint/parser": "^7.1.1", 38 | "eslint": "^8.57.0", 39 | "eslint-config-next": "^14.1.3", 40 | "postcss": "^8.4.34", 41 | "prettier": "^3.2.5", 42 | "prettier-plugin-tailwindcss": "^0.5.11", 43 | "prisma": "^5.10.2", 44 | "tailwindcss": "^3.4.1", 45 | "typescript": "^5.4.2" 46 | }, 47 | "ct3aMetadata": { 48 | "initVersion": "7.30.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /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 | - [Drizzle](https://orm.drizzle.team) 15 | - [Tailwind CSS](https://tailwindcss.com) 16 | - [tRPC](https://trpc.io) 17 | 18 | ## Learn More 19 | 20 | To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: 21 | 22 | - [Documentation](https://create.t3.gg/) 23 | - [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials 24 | 25 | You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! 26 | 27 | ## How do I deploy this? 28 | 29 | 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. 30 | -------------------------------------------------------------------------------- /start-database.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to start a docker container for a local development database 3 | 4 | # TO RUN ON WINDOWS: 5 | # 1. Install WSL (Windows Subsystem for Linux) - https://learn.microsoft.com/en-us/windows/wsl/install 6 | # 2. Install Docker Desktop for Windows - https://docs.docker.com/docker-for-windows/install/ 7 | # 3. Open WSL - `wsl` 8 | # 4. Run this script - `./start-database.sh` 9 | 10 | # On Linux and macOS you can run this script directly - `./start-database.sh` 11 | 12 | DB_CONTAINER_NAME=".-postgres" 13 | 14 | if ! [ -x "$(command -v docker)" ]; then 15 | echo -e "Docker is not installed. Please install docker and try again.\nDocker install guide: https://docs.docker.com/engine/install/" 16 | exit 1 17 | fi 18 | 19 | if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then 20 | echo "Database container '$DB_CONTAINER_NAME' already running" 21 | exit 0 22 | fi 23 | 24 | if [ "$(docker ps -q -a -f name=$DB_CONTAINER_NAME)" ]; then 25 | docker start "$DB_CONTAINER_NAME" 26 | echo "Existing database container '$DB_CONTAINER_NAME' started" 27 | exit 0 28 | fi 29 | 30 | # import env variables from .env 31 | set -a 32 | source .env 33 | 34 | DB_PASSWORD=$(echo "$DATABASE_URL" | awk -F':' '{print $3}' | awk -F'@' '{print $1}') 35 | 36 | if [ "$DB_PASSWORD" = "password" ]; then 37 | echo "You are using the default database password" 38 | read -p "Should we generate a random password for you? [y/N]: " -r REPLY 39 | if ! [[ $REPLY =~ ^[Yy]$ ]]; then 40 | echo "Please set a password in the .env file and try again" 41 | exit 1 42 | fi 43 | # Generate a random URL-safe password 44 | DB_PASSWORD=$(openssl rand -base64 12 | tr '+/' '-_') 45 | sed -i -e "s#:password@#:$DB_PASSWORD@#" .env 46 | fi 47 | 48 | docker run -d \ 49 | --name $DB_CONTAINER_NAME \ 50 | -e POSTGRES_PASSWORD="$DB_PASSWORD" \ 51 | -e POSTGRES_DB=. \ 52 | -p 5432:5432 \ 53 | docker.io/postgres && echo "Database container '$DB_CONTAINER_NAME' was successfully created" 54 | -------------------------------------------------------------------------------- /src/env.js: -------------------------------------------------------------------------------- 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.string().url(), 11 | NODE_ENV: z 12 | .enum(["development", "test", "production"]) 13 | .default("development"), 14 | NEXTAUTH_SECRET: 15 | process.env.NODE_ENV === "production" 16 | ? z.string() 17 | : z.string().optional(), 18 | NEXTAUTH_URL: z.preprocess( 19 | // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL 20 | // Since NextAuth.js automatically uses the VERCEL_URL if present. 21 | (str) => process.env.VERCEL_URL ?? str, 22 | // VERCEL_URL doesn't include `https` so it cant be validated as a URL 23 | process.env.VERCEL ? z.string() : z.string().url() 24 | ), 25 | DISCORD_CLIENT_ID: z.string(), 26 | DISCORD_CLIENT_SECRET: z.string(), 27 | }, 28 | 29 | /** 30 | * Specify your client-side environment variables schema here. This way you can ensure the app 31 | * isn't built with invalid env vars. To expose them to the client, prefix them with 32 | * `NEXT_PUBLIC_`. 33 | */ 34 | client: { 35 | // NEXT_PUBLIC_CLIENTVAR: z.string(), 36 | }, 37 | 38 | /** 39 | * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. 40 | * middlewares) or client-side so we need to destruct manually. 41 | */ 42 | runtimeEnv: { 43 | DATABASE_URL: process.env.DATABASE_URL, 44 | NODE_ENV: process.env.NODE_ENV, 45 | NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, 46 | NEXTAUTH_URL: process.env.NEXTAUTH_URL, 47 | DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID, 48 | DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET, 49 | }, 50 | /** 51 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially 52 | * useful for Docker builds. 53 | */ 54 | skipValidation: !!process.env.SKIP_ENV_VALIDATION, 55 | /** 56 | * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and 57 | * `SOME_VAR=''` will throw an error. 58 | */ 59 | emptyStringAsUndefined: true, 60 | }); 61 | -------------------------------------------------------------------------------- /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 = "postgresql" 10 | // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below 11 | // Further reading: 12 | // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema 13 | // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string 14 | url = env("DATABASE_URL") 15 | } 16 | 17 | model Post { 18 | id Int @id @default(autoincrement()) 19 | name String 20 | createdAt DateTime @default(now()) 21 | updatedAt DateTime @updatedAt 22 | 23 | createdBy User @relation(fields: [createdById], references: [id]) 24 | createdById String 25 | 26 | @@index([name]) 27 | } 28 | 29 | // Necessary for Next auth 30 | model Account { 31 | id String @id @default(cuid()) 32 | userId String 33 | type String 34 | provider String 35 | providerAccountId String 36 | refresh_token String? // @db.Text 37 | access_token String? // @db.Text 38 | expires_at Int? 39 | token_type String? 40 | scope String? 41 | id_token String? // @db.Text 42 | session_state String? 43 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 44 | 45 | @@unique([provider, providerAccountId]) 46 | } 47 | 48 | model Session { 49 | id String @id @default(cuid()) 50 | sessionToken String @unique 51 | userId String 52 | expires DateTime 53 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 54 | } 55 | 56 | model User { 57 | id String @id @default(cuid()) 58 | name String? 59 | email String? @unique 60 | emailVerified DateTime? 61 | image String? 62 | accounts Account[] 63 | sessions Session[] 64 | posts Post[] 65 | } 66 | 67 | model VerificationToken { 68 | identifier String 69 | token String @unique 70 | expires DateTime 71 | 72 | @@unique([identifier, token]) 73 | } 74 | -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the client-side entrypoint for your tRPC API. It is used to create the `api` object which 3 | * contains the Next.js App-wrapper, as well as your type-safe React Query hooks. 4 | * 5 | * We also create a few inference helpers for input and output types. 6 | */ 7 | import { httpBatchLink, loggerLink } from "@trpc/client"; 8 | import { createTRPCNext } from "@trpc/next"; 9 | import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; 10 | import superjson from "superjson"; 11 | 12 | import { type AppRouter } from "~/server/api/root"; 13 | 14 | const getBaseUrl = () => { 15 | if (typeof window !== "undefined") return ""; // browser should use relative url 16 | if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url 17 | return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost 18 | }; 19 | 20 | /** A set of type-safe react-query hooks for your tRPC API. */ 21 | export const api = createTRPCNext({ 22 | config() { 23 | return { 24 | /** 25 | * Links used to determine request flow from client to server. 26 | * 27 | * @see https://trpc.io/docs/links 28 | */ 29 | links: [ 30 | loggerLink({ 31 | enabled: (opts) => 32 | process.env.NODE_ENV === "development" || 33 | (opts.direction === "down" && opts.result instanceof Error), 34 | }), 35 | httpBatchLink({ 36 | /** 37 | * Transformer used for data de-serialization from the server. 38 | * 39 | * @see https://trpc.io/docs/data-transformers 40 | */ 41 | transformer: superjson, 42 | url: `${getBaseUrl()}/api/trpc`, 43 | }), 44 | ], 45 | }; 46 | }, 47 | /** 48 | * Whether tRPC should await queries when server rendering pages. 49 | * 50 | * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false 51 | */ 52 | ssr: false, 53 | transformer: superjson, 54 | }); 55 | 56 | /** 57 | * Inference helper for inputs. 58 | * 59 | * @example type HelloInput = RouterInputs['example']['hello'] 60 | */ 61 | export type RouterInputs = inferRouterInputs; 62 | 63 | /** 64 | * Inference helper for outputs. 65 | * 66 | * @example type HelloOutput = RouterOutputs['example']['hello'] 67 | */ 68 | export type RouterOutputs = inferRouterOutputs; 69 | -------------------------------------------------------------------------------- /src/server/auth.ts: -------------------------------------------------------------------------------- 1 | import { PrismaAdapter } from "@auth/prisma-adapter"; 2 | import { type GetServerSidePropsContext } from "next"; 3 | import { 4 | getServerSession, 5 | type DefaultSession, 6 | type NextAuthOptions, 7 | } from "next-auth"; 8 | import { type Adapter } from "next-auth/adapters"; 9 | import DiscordProvider from "next-auth/providers/discord"; 10 | 11 | import { env } from "~/env"; 12 | import { db } from "~/server/db"; 13 | 14 | /** 15 | * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` 16 | * object and keep type safety. 17 | * 18 | * @see https://next-auth.js.org/getting-started/typescript#module-augmentation 19 | */ 20 | declare module "next-auth" { 21 | interface Session extends DefaultSession { 22 | user: DefaultSession["user"] & { 23 | id: string; 24 | // ...other properties 25 | // role: UserRole; 26 | }; 27 | } 28 | 29 | // interface User { 30 | // // ...other properties 31 | // // role: UserRole; 32 | // } 33 | } 34 | 35 | /** 36 | * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. 37 | * 38 | * @see https://next-auth.js.org/configuration/options 39 | */ 40 | export const authOptions: NextAuthOptions = { 41 | callbacks: { 42 | session: ({ session, user }) => ({ 43 | ...session, 44 | user: { 45 | ...session.user, 46 | id: user.id, 47 | }, 48 | }), 49 | }, 50 | adapter: PrismaAdapter(db) as Adapter, 51 | providers: [ 52 | DiscordProvider({ 53 | clientId: env.DISCORD_CLIENT_ID, 54 | clientSecret: env.DISCORD_CLIENT_SECRET, 55 | }), 56 | /** 57 | * ...add more providers here. 58 | * 59 | * Most other providers require a bit more work than the Discord provider. For example, the 60 | * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account 61 | * model. Refer to the NextAuth.js docs for the provider you want to use. Example: 62 | * 63 | * @see https://next-auth.js.org/providers/github 64 | */ 65 | ], 66 | }; 67 | 68 | /** 69 | * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file. 70 | * 71 | * @see https://next-auth.js.org/configuration/nextjs 72 | */ 73 | export const getServerAuthSession = (ctx: { 74 | req: GetServerSidePropsContext["req"]; 75 | res: GetServerSidePropsContext["res"]; 76 | }) => { 77 | return getServerSession(ctx.req, ctx.res, authOptions); 78 | }; 79 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { signIn, signOut, useSession } from "next-auth/react"; 2 | import Head from "next/head"; 3 | import Link from "next/link"; 4 | 5 | import { api } from "~/utils/api"; 6 | 7 | export default function Home() { 8 | const hello = api.post.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 |

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

50 | 51 |
52 |
53 |
54 | 55 | ); 56 | } 57 | 58 | function AuthShowcase() { 59 | const { data: sessionData } = useSession(); 60 | 61 | const { data: secretMessage } = api.post.getSecretMessage.useQuery( 62 | undefined, // no input 63 | { enabled: sessionData?.user !== undefined } 64 | ); 65 | 66 | return ( 67 |
68 |

69 | {sessionData && Logged in as {sessionData.user?.name}} 70 | {secretMessage && - {secretMessage}} 71 |

72 | 78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /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 | 10 | import { initTRPC, TRPCError } from "@trpc/server"; 11 | import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; 12 | import { type Session } from "next-auth"; 13 | import superjson from "superjson"; 14 | import { ZodError } from "zod"; 15 | 16 | import { getServerAuthSession } from "~/server/auth"; 17 | import { db } from "~/server/db"; 18 | 19 | /** 20 | * 1. CONTEXT 21 | * 22 | * This section defines the "contexts" that are available in the backend API. 23 | * 24 | * These allow you to access things when processing a request, like the database, the session, etc. 25 | */ 26 | 27 | interface CreateContextOptions { 28 | session: Session | null; 29 | } 30 | 31 | /** 32 | * This helper generates the "internals" for a tRPC context. If you need to use it, you can export 33 | * it from here. 34 | * 35 | * Examples of things you may need it for: 36 | * - testing, so we don't have to mock Next.js' req/res 37 | * - tRPC's `createSSGHelpers`, where we don't have req/res 38 | * 39 | * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts 40 | */ 41 | const createInnerTRPCContext = (opts: CreateContextOptions) => { 42 | return { 43 | session: opts.session, 44 | db, 45 | }; 46 | }; 47 | 48 | /** 49 | * This is the actual context you will use in your router. It will be used to process every request 50 | * that goes through your tRPC endpoint. 51 | * 52 | * @see https://trpc.io/docs/context 53 | */ 54 | export const createTRPCContext = async (opts: CreateNextContextOptions) => { 55 | const { req, res } = opts; 56 | 57 | // Get the session from the server using the getServerSession wrapper function 58 | const session = await getServerAuthSession({ req, res }); 59 | 60 | return createInnerTRPCContext({ 61 | session, 62 | }); 63 | }; 64 | 65 | /** 66 | * 2. INITIALIZATION 67 | * 68 | * This is where the tRPC API is initialized, connecting the context and transformer. We also parse 69 | * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation 70 | * errors on the backend. 71 | */ 72 | 73 | const t = initTRPC.context().create({ 74 | transformer: superjson, 75 | errorFormatter({ shape, error }) { 76 | return { 77 | ...shape, 78 | data: { 79 | ...shape.data, 80 | zodError: 81 | error.cause instanceof ZodError ? error.cause.flatten() : null, 82 | }, 83 | }; 84 | }, 85 | }); 86 | 87 | /** 88 | * Create a server-side caller. 89 | * 90 | * @see https://trpc.io/docs/server/server-side-calls 91 | */ 92 | export const createCallerFactory = t.createCallerFactory; 93 | 94 | /** 95 | * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) 96 | * 97 | * These are the pieces you use to build your tRPC API. You should import these a lot in the 98 | * "/src/server/api/routers" directory. 99 | */ 100 | 101 | /** 102 | * This is how you create new routers and sub-routers in your tRPC API. 103 | * 104 | * @see https://trpc.io/docs/router 105 | */ 106 | export const createTRPCRouter = t.router; 107 | 108 | /** 109 | * Public (unauthenticated) procedure 110 | * 111 | * This is the base piece you use to build new queries and mutations on your tRPC API. It does not 112 | * guarantee that a user querying is authorized, but you can still access user session data if they 113 | * are logged in. 114 | */ 115 | export const publicProcedure = t.procedure; 116 | 117 | /** 118 | * Protected (authenticated) procedure 119 | * 120 | * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies 121 | * the session is valid and guarantees `ctx.session.user` is not null. 122 | * 123 | * @see https://trpc.io/docs/procedures 124 | */ 125 | export const protectedProcedure = t.procedure.use(({ ctx, next }) => { 126 | if (!ctx.session || !ctx.session.user) { 127 | throw new TRPCError({ code: "UNAUTHORIZED" }); 128 | } 129 | return next({ 130 | ctx: { 131 | // infers the `session` as non-nullable 132 | session: { ...ctx.session, user: ctx.session.user }, 133 | }, 134 | }); 135 | }); 136 | --------------------------------------------------------------------------------