├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prettier.config.cjs ├── prisma └── schema.prisma ├── public └── favicon.ico ├── src ├── env │ ├── client.mjs │ ├── schema.mjs │ └── server.mjs ├── pages │ ├── _app.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ ├── examples.ts │ │ ├── restricted.ts │ │ └── trpc │ │ │ └── [trpc].ts │ └── index.tsx ├── server │ ├── common │ │ └── get-server-auth-session.ts │ ├── db │ │ └── client.ts │ └── trpc │ │ ├── context.ts │ │ ├── router │ │ ├── _app.ts │ │ ├── auth.ts │ │ └── example.ts │ │ └── trpc.ts ├── styles │ └── globals.css ├── types │ └── next-auth.d.ts └── utils │ └── trpc.ts ├── tailwind.config.cjs └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo. 2 | # Keep this file up-to-date when you add new variables to `.env`. 3 | 4 | # This file will be committed to version control, so make sure not to have any secrets in it. 5 | # If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets. 6 | 7 | # When adding additional env variables, the schema in /env/schema.mjs should be updated accordingly 8 | 9 | # Prisma 10 | DATABASE_URL=file:./db.sqlite 11 | 12 | # Next Auth 13 | # You can generate the secret via 'openssl rand -base64 32' on Linux 14 | # More info: https://next-auth.js.org/configuration/options#secret 15 | # NEXTAUTH_SECRET= 16 | NEXTAUTH_URL=http://localhost:3000 17 | 18 | # Next Auth Discord Provider 19 | DISCORD_CLIENT_ID= 20 | DISCORD_CLIENT_SECRET= 21 | -------------------------------------------------------------------------------- /.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"], 8 | "rules": { 9 | "@typescript-eslint/consistent-type-imports": "warn" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.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) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. 29 | -------------------------------------------------------------------------------- /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 | swcMinify: true, 12 | i18n: { 13 | locales: ["en"], 14 | defaultLocale: "en", 15 | }, 16 | }; 17 | export default config; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rewire-t3-app", 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 | "@next-auth/prisma-adapter": "1.0.5", 14 | "@prisma/client": "^4.5.0", 15 | "@tanstack/react-query": "^4.16.0", 16 | "@trpc/client": "^10.0.0", 17 | "@trpc/next": "^10.0.0", 18 | "@trpc/react-query": "^10.0.0", 19 | "@trpc/server": "^10.0.0", 20 | "next": "13.0.2", 21 | "next-auth": "4.17.0", 22 | "react": "18.2.0", 23 | "react-dom": "18.2.0", 24 | "superjson": "1.9.1", 25 | "zod": "^3.18.0" 26 | }, 27 | "devDependencies": { 28 | "@types/node": "^18.0.0", 29 | "@types/react": "^18.0.14", 30 | "@types/react-dom": "^18.0.5", 31 | "@typescript-eslint/eslint-plugin": "^5.33.0", 32 | "@typescript-eslint/parser": "^5.33.0", 33 | "autoprefixer": "^10.4.7", 34 | "eslint": "^8.26.0", 35 | "eslint-config-next": "13.0.2", 36 | "postcss": "^8.4.14", 37 | "prettier": "^2.7.1", 38 | "prettier-plugin-tailwindcss": "^0.1.13", 39 | "prisma": "^4.5.0", 40 | "tailwindcss": "^3.2.0", 41 | "typescript": "^4.8.4" 42 | }, 43 | "ct3aMetadata": { 44 | "initVersion": "6.11.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // NOTE: When using postgresql, 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 Example { 18 | id String @id @default(cuid()) 19 | createdAt DateTime @default(now()) 20 | updatedAt DateTime @updatedAt 21 | } 22 | 23 | // Necessary for Next auth 24 | model Account { 25 | id String @id @default(cuid()) 26 | userId String 27 | type String 28 | provider String 29 | providerAccountId String 30 | refresh_token String? // @db.Text 31 | access_token String? // @db.Text 32 | expires_at Int? 33 | token_type String? 34 | scope String? 35 | id_token String? // @db.Text 36 | session_state String? 37 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 38 | 39 | @@unique([provider, providerAccountId]) 40 | } 41 | 42 | model Session { 43 | id String @id @default(cuid()) 44 | sessionToken String @unique 45 | userId String 46 | expires DateTime 47 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 48 | } 49 | 50 | model User { 51 | id String @id @default(cuid()) 52 | name String? 53 | email String? @unique 54 | emailVerified DateTime? 55 | image String? 56 | accounts Account[] 57 | sessions Session[] 58 | } 59 | 60 | model VerificationToken { 61 | identifier String 62 | token String @unique 63 | expires DateTime 64 | 65 | @@unique([identifier, token]) 66 | } 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t3dotgg/rewire-t3-app/1ef6abd5a24fdc54ebb722b6c7c135f0f2d144a2/public/favicon.ico -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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(), 10 | NODE_ENV: z.enum(["development", "test", "production"]), 11 | NEXTAUTH_SECRET: 12 | process.env.NODE_ENV === "production" 13 | ? z.string().min(1) 14 | : z.string().min(1).optional(), 15 | NEXTAUTH_URL: z.preprocess( 16 | // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL 17 | // Since NextAuth.js automatically uses the VERCEL_URL if present. 18 | (str) => process.env.VERCEL_URL ?? str, 19 | // VERCEL_URL doesn't include `https` so it cant be validated as a URL 20 | process.env.VERCEL ? z.string() : z.string().url(), 21 | ), 22 | DISCORD_CLIENT_ID: z.string(), 23 | DISCORD_CLIENT_SECRET: z.string(), 24 | }); 25 | 26 | /** 27 | * Specify your client-side environment variables schema here. 28 | * This way you can ensure the app isn't built with invalid env vars. 29 | * To expose them to the client, prefix them with `NEXT_PUBLIC_`. 30 | */ 31 | export const clientSchema = z.object({ 32 | // NEXT_PUBLIC_CLIENTVAR: z.string(), 33 | }); 34 | 35 | /** 36 | * You can't destruct `process.env` as a regular object, so you have to do 37 | * it manually here. This is because Next.js evaluates this at build time, 38 | * and only used environment variables are included in the build. 39 | * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} 40 | */ 41 | export const clientEnv = { 42 | // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, 43 | }; 44 | -------------------------------------------------------------------------------- /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 } from "./schema.mjs"; 7 | import { env as clientEnv, formatErrors } from "./client.mjs"; 8 | 9 | const _serverEnv = serverSchema.safeParse(process.env); 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 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { type AppType } from "next/app"; 2 | import { type Session } from "next-auth"; 3 | import { SessionProvider } from "next-auth/react"; 4 | 5 | import { trpc } from "../utils/trpc"; 6 | 7 | import "../styles/globals.css"; 8 | 9 | const MyApp: AppType<{ session: Session | null }> = ({ 10 | Component, 11 | pageProps: { session, ...pageProps }, 12 | }) => { 13 | return ( 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default trpc.withTRPC(MyApp); 21 | -------------------------------------------------------------------------------- /src/pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | import NextAuth, { type NextAuthOptions } from "next-auth"; 2 | import DiscordProvider from "next-auth/providers/discord"; 3 | // Prisma adapter for NextAuth, optional and can be removed 4 | import { PrismaAdapter } from "@next-auth/prisma-adapter"; 5 | 6 | import { env } from "../../../env/server.mjs"; 7 | import { prisma } from "../../../server/db/client"; 8 | 9 | export const authOptions: NextAuthOptions = { 10 | // Include user.id on session 11 | callbacks: { 12 | session({ session, user }) { 13 | if (session.user) { 14 | session.user.id = user.id; 15 | } 16 | return session; 17 | }, 18 | }, 19 | // Configure one or more authentication providers 20 | adapter: PrismaAdapter(prisma), 21 | providers: [ 22 | DiscordProvider({ 23 | clientId: env.DISCORD_CLIENT_ID, 24 | clientSecret: env.DISCORD_CLIENT_SECRET, 25 | }), 26 | // ...add more providers here 27 | ], 28 | }; 29 | 30 | export default NextAuth(authOptions); 31 | -------------------------------------------------------------------------------- /src/pages/api/examples.ts: -------------------------------------------------------------------------------- 1 | import { type NextApiRequest, type NextApiResponse } from "next"; 2 | 3 | import { prisma } from "../../server/db/client"; 4 | 5 | const examples = async (req: NextApiRequest, res: NextApiResponse) => { 6 | const examples = await prisma.example.findMany(); 7 | res.status(200).json(examples); 8 | }; 9 | 10 | export default examples; 11 | -------------------------------------------------------------------------------- /src/pages/api/restricted.ts: -------------------------------------------------------------------------------- 1 | import { type NextApiRequest, type NextApiResponse } from "next"; 2 | 3 | import { getServerAuthSession } from "../../server/common/get-server-auth-session"; 4 | 5 | const restricted = async (req: NextApiRequest, res: NextApiResponse) => { 6 | const session = await getServerAuthSession({ req, res }); 7 | 8 | if (session) { 9 | res.send({ 10 | content: 11 | "This is protected content. You can access this content because you are signed in.", 12 | }); 13 | } else { 14 | res.send({ 15 | error: 16 | "You must be signed in to view the protected content on this page.", 17 | }); 18 | } 19 | }; 20 | 21 | export default restricted; 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 { createContext } from "../../../server/trpc/context"; 5 | import { appRouter } from "../../../server/trpc/router/_app"; 6 | 7 | // export API handler 8 | export default createNextApiHandler({ 9 | router: appRouter, 10 | createContext, 11 | onError: 12 | env.NODE_ENV === "development" 13 | ? ({ path, error }) => { 14 | console.error(`❌ tRPC failed on ${path}: ${error}`); 15 | } 16 | : undefined, 17 | }); 18 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next"; 2 | import Head from "next/head"; 3 | import Link from "next/link"; 4 | import { signIn, signOut, useSession } from "next-auth/react"; 5 | 6 | import { trpc } from "../utils/trpc"; 7 | 8 | const Home: NextPage = () => { 9 | const hello = trpc.example.hello.useQuery({ text: "from tRPC" }); 10 | 11 | return ( 12 | <> 13 | 14 | Create T3 App 15 | 16 | 17 | 18 |
19 |
20 |

21 | Create T3 App 22 |

23 |
24 | 28 |

First Steps →

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

Documentation →

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

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

49 | 50 |
51 |
52 |
53 | 54 | ); 55 | }; 56 | 57 | export default Home; 58 | 59 | const AuthShowcase: React.FC = () => { 60 | const { data: sessionData } = useSession(); 61 | 62 | const { data: secretMessage } = trpc.auth.getSecretMessage.useQuery( 63 | undefined, // no input 64 | { enabled: sessionData?.user !== undefined }, 65 | ); 66 | 67 | return ( 68 |
69 |

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

73 | 79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /src/server/common/get-server-auth-session.ts: -------------------------------------------------------------------------------- 1 | import { type GetServerSidePropsContext } from "next"; 2 | import { unstable_getServerSession } from "next-auth"; 3 | 4 | import { authOptions } from "../../pages/api/auth/[...nextauth]"; 5 | 6 | /** 7 | * Wrapper for unstable_getServerSession https://next-auth.js.org/configuration/nextjs 8 | * See example usage in trpc createContext or the restricted API route 9 | */ 10 | export const getServerAuthSession = async (ctx: { 11 | req: GetServerSidePropsContext["req"]; 12 | res: GetServerSidePropsContext["res"]; 13 | }) => { 14 | return await unstable_getServerSession(ctx.req, ctx.res, authOptions); 15 | }; 16 | -------------------------------------------------------------------------------- /src/server/db/client.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | import { env } from "../../env/server.mjs"; 4 | 5 | declare global { 6 | // eslint-disable-next-line no-var 7 | var prisma: PrismaClient | undefined; 8 | } 9 | 10 | export const prisma = 11 | global.prisma || 12 | new PrismaClient({ 13 | log: 14 | env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], 15 | }); 16 | 17 | if (env.NODE_ENV !== "production") { 18 | global.prisma = prisma; 19 | } 20 | -------------------------------------------------------------------------------- /src/server/trpc/context.ts: -------------------------------------------------------------------------------- 1 | import { type inferAsyncReturnType } from "@trpc/server"; 2 | import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; 3 | import { type Session } from "next-auth"; 4 | 5 | import { getServerAuthSession } from "../common/get-server-auth-session"; 6 | import { prisma } from "../db/client"; 7 | 8 | type CreateContextOptions = { 9 | session: Session | null; 10 | }; 11 | 12 | /** Use this helper for: 13 | * - testing, so we dont have to mock Next.js' req/res 14 | * - trpc's `createSSGHelpers` where we don't have req/res 15 | * @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts 16 | **/ 17 | export const createContextInner = async (opts: CreateContextOptions) => { 18 | return { 19 | session: opts.session, 20 | prisma, 21 | }; 22 | }; 23 | 24 | /** 25 | * This is the actual context you'll use in your router 26 | * @link https://trpc.io/docs/context 27 | **/ 28 | export const createContext = async (opts: CreateNextContextOptions) => { 29 | const { req, res } = opts; 30 | 31 | // Get the session from the server using the unstable_getServerSession wrapper function 32 | const session = await getServerAuthSession({ req, res }); 33 | 34 | return await createContextInner({ 35 | session, 36 | }); 37 | }; 38 | 39 | export type Context = inferAsyncReturnType; 40 | -------------------------------------------------------------------------------- /src/server/trpc/router/_app.ts: -------------------------------------------------------------------------------- 1 | import { router } from "../trpc"; 2 | import { authRouter } from "./auth"; 3 | import { exampleRouter } from "./example"; 4 | 5 | export const appRouter = router({ 6 | example: exampleRouter, 7 | auth: authRouter, 8 | }); 9 | 10 | // export type definition of API 11 | export type AppRouter = typeof appRouter; 12 | -------------------------------------------------------------------------------- /src/server/trpc/router/auth.ts: -------------------------------------------------------------------------------- 1 | import { router, publicProcedure, protectedProcedure } from "../trpc"; 2 | 3 | export const authRouter = router({ 4 | getSession: publicProcedure.query(({ ctx }) => { 5 | return ctx.session; 6 | }), 7 | getSecretMessage: protectedProcedure.query(() => { 8 | return "you can now see this secret message!"; 9 | }), 10 | }); 11 | -------------------------------------------------------------------------------- /src/server/trpc/router/example.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | import { router, publicProcedure } from "../trpc"; 4 | 5 | export const exampleRouter = router({ 6 | hello: publicProcedure 7 | .input(z.object({ text: z.string().nullish() }).nullish()) 8 | .query(({ input }) => { 9 | return { 10 | greeting: `Hello ${input?.text ?? "world"}`, 11 | }; 12 | }), 13 | getAll: publicProcedure.query(({ ctx }) => { 14 | return ctx.prisma.example.findMany(); 15 | }), 16 | }); 17 | -------------------------------------------------------------------------------- /src/server/trpc/trpc.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC, TRPCError } from "@trpc/server"; 2 | import superjson from "superjson"; 3 | 4 | import { type Context } from "./context"; 5 | 6 | const t = initTRPC.context().create({ 7 | transformer: superjson, 8 | errorFormatter({ shape }) { 9 | return shape; 10 | }, 11 | }); 12 | 13 | export const router = t.router; 14 | 15 | /** 16 | * Unprotected procedure 17 | **/ 18 | export const publicProcedure = t.procedure; 19 | 20 | /** 21 | * Reusable middleware to ensure 22 | * users are logged in 23 | */ 24 | const isAuthed = t.middleware(({ ctx, next }) => { 25 | if (!ctx.session || !ctx.session.user) { 26 | throw new TRPCError({ code: "UNAUTHORIZED" }); 27 | } 28 | return next({ 29 | ctx: { 30 | // infers the `session` as non-nullable 31 | session: { ...ctx.session, user: ctx.session.user }, 32 | }, 33 | }); 34 | }); 35 | 36 | /** 37 | * Protected procedure 38 | **/ 39 | export const protectedProcedure = t.procedure.use(isAuthed); 40 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/types/next-auth.d.ts: -------------------------------------------------------------------------------- 1 | import { type DefaultSession } from "next-auth"; 2 | 3 | declare module "next-auth" { 4 | /** 5 | * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context 6 | */ 7 | interface Session { 8 | user?: { 9 | id: string; 10 | } & DefaultSession["user"]; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/trpc.ts: -------------------------------------------------------------------------------- 1 | import { httpBatchLink, loggerLink } from "@trpc/client"; 2 | import { createTRPCNext } from "@trpc/next"; 3 | import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; 4 | import superjson from "superjson"; 5 | 6 | import { type AppRouter } from "../server/trpc/router/_app"; 7 | 8 | const getBaseUrl = () => { 9 | if (typeof window !== "undefined") return ""; // browser should use relative url 10 | if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url 11 | return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost 12 | }; 13 | 14 | export const trpc = createTRPCNext({ 15 | config() { 16 | return { 17 | transformer: superjson, 18 | links: [ 19 | loggerLink({ 20 | enabled: (opts) => 21 | process.env.NODE_ENV === "development" || 22 | (opts.direction === "down" && opts.result instanceof Error), 23 | }), 24 | httpBatchLink({ 25 | url: `${getBaseUrl()}/api/trpc`, 26 | }), 27 | ], 28 | }; 29 | }, 30 | ssr: false, 31 | }); 32 | 33 | /** 34 | * Inference helper for inputs 35 | * @example type HelloInput = RouterInputs['example']['hello'] 36 | **/ 37 | export type RouterInputs = inferRouterInputs; 38 | /** 39 | * Inference helper for outputs 40 | * @example type HelloOutput = RouterOutputs['example']['hello'] 41 | **/ 42 | export type RouterOutputs = inferRouterOutputs; 43 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------