···11+# Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo.
22+# Keep this file up-to-date when you add new variables to \`.env\`.
33+44+# This file will be committed to version control, so make sure not to have any secrets in it.
55+# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.
66+77+# We use dotenv to load Prisma from Next.js' .env file
88+# @see https://www.prisma.io/docs/reference/database-reference/connection-urls
99+DATABASE_URL=file:./db.sqlite
1010+1111+# @see https://next-auth.js.org/configuration/options#nextauth_url
1212+NEXTAUTH_URL=http://localhost:3000
1313+1414+# You can generate the secret via 'openssl rand -base64 32' on Unix
1515+# @see https://next-auth.js.org/configuration/options#secret
1616+NEXTAUTH_SECRET=supersecret
1717+1818+# Preconfigured Discord OAuth provider, works out-of-the-box
1919+# @see https://next-auth.js.org/providers/discord
2020+DISCORD_CLIENT_ID=
2121+DISCORD_CLIENT_SECRET=
···11+# These are supported funding model platforms
22+33+github: juliusmarminge
+37
.github/ISSUE_TEMPLATE/bug_report.yml
···11+name: 🐞 Bug Report
22+description: Create a bug report to help us improve
33+title: "bug: "
44+labels: ["🐞❔ unconfirmed bug"]
55+body:
66+ - type: textarea
77+ attributes:
88+ label: Provide environment information
99+ description: |
1010+ Run this command in your project root and paste the results in a code block:
1111+ ```bash
1212+ npx envinfo --system --binaries
1313+ ```
1414+ validations:
1515+ required: true
1616+ - type: textarea
1717+ attributes:
1818+ label: Describe the bug
1919+ description: A clear and concise description of the bug, as well as what you expected to happen when encountering it.
2020+ validations:
2121+ required: true
2222+ - type: input
2323+ attributes:
2424+ label: Link to reproduction
2525+ description: Please provide a link to a reproduction of the bug. Issues without a reproduction repo may be ignored.
2626+ validations:
2727+ required: true
2828+ - type: textarea
2929+ attributes:
3030+ label: To reproduce
3131+ description: Describe how to reproduce your bug. Steps, code snippets, reproduction repos etc.
3232+ validations:
3333+ required: true
3434+ - type: textarea
3535+ attributes:
3636+ label: Additional information
3737+ description: Add any other information related to the bug here, screenshots if applicable.
+29
.github/ISSUE_TEMPLATE/feature_request.yml
···11+# This template is heavily inspired by the Next.js's template:
22+# See here: https://github.com/vercel/next.js/blob/canary/.github/ISSUE_TEMPLATE/3.feature_request.yml
33+44+name: 🛠 Feature Request
55+description: Create a feature request for the core packages
66+title: 'feat: '
77+labels: ['✨ enhancement']
88+body:
99+ - type: markdown
1010+ attributes:
1111+ value: |
1212+ Thank you for taking the time to file a feature request. Please fill out this form as completely as possible.
1313+ - type: textarea
1414+ attributes:
1515+ label: Describe the feature you'd like to request
1616+ description: Please describe the feature as clear and concise as possible. Remember to add context as to why you believe this feature is needed.
1717+ validations:
1818+ required: true
1919+ - type: textarea
2020+ attributes:
2121+ label: Describe the solution you'd like to see
2222+ description: Please describe the solution you would like to see. Adding example usage is a good way to provide context.
2323+ validations:
2424+ required: true
2525+ - type: textarea
2626+ attributes:
2727+ label: Additional information
2828+ description: Add any other information related to the feature here. If your feature request is related to any issues or discussions, link them here.
2929+
+61
.github/workflows/ci.yml
···11+name: CI
22+33+on:
44+ pull_request:
55+ branches: ["*"]
66+ push:
77+ branches: ["main"]
88+ merge_group:
99+1010+# You can leverage Vercel Remote Caching with Turbo to speed up your builds
1111+# @link https://turborepo.org/docs/core-concepts/remote-caching#remote-caching-on-vercel-builds
1212+env:
1313+ TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
1414+ TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
1515+1616+jobs:
1717+ build-lint:
1818+ env:
1919+ DATABASE_URL: file:./db.sqlite
2020+ runs-on: ubuntu-latest
2121+2222+ steps:
2323+ - name: Checkout repo
2424+ uses: actions/checkout@v3
2525+2626+ - name: Setup pnpm
2727+ uses: pnpm/action-setup@v2.2.4
2828+2929+ - name: Setup Node 18
3030+ uses: actions/setup-node@v3
3131+ with:
3232+ node-version: 18
3333+3434+ - name: Get pnpm store directory
3535+ id: pnpm-cache
3636+ run: |
3737+ echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
3838+3939+ - name: Setup pnpm cache
4040+ uses: actions/cache@v3
4141+ with:
4242+ path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
4343+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
4444+ restore-keys: |
4545+ ${{ runner.os }}-pnpm-store-
4646+4747+ - name: Install deps (with cache)
4848+ run: pnpm install
4949+5050+ # Normally, this would be done as part of the turbo pipeline - however since the Expo app doesn't depend on `@acme/db` it doesn't care.
5151+ # TODO: Free for all to find a better solution here.
5252+ - name: Generate Prisma Client
5353+ run: pnpm turbo db:generate
5454+5555+ - name: Build, lint and type-check
5656+ run: pnpm turbo build lint type-check
5757+ env:
5858+ SKIP_ENV_VALIDATION: true
5959+6060+ - name: Check workspaces
6161+ run: pnpm manypkg check
+48
.gitignore
···11+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
22+33+# dependencies
44+node_modules
55+.pnp
66+.pnp.js
77+88+# testing
99+coverage
1010+1111+# database
1212+**/prisma/db.sqlite
1313+**/prisma/db.sqlite-journal
1414+1515+# next.js
1616+.next/
1717+out/
1818+next-env.d.ts
1919+2020+# expo
2121+.expo/
2222+dist/
2323+2424+# production
2525+build
2626+2727+# misc
2828+.DS_Store
2929+*.pem
3030+3131+# debug
3232+npm-debug.log*
3333+yarn-debug.log*
3434+yarn-error.log*
3535+.pnpm-debug.log*
3636+3737+# local env files
3838+.env
3939+.env*.local
4040+4141+# vercel
4242+.vercel
4343+4444+# typescript
4545+*.tsbuildinfo
4646+4747+# turbo
4848+.turbo
+18
.npmrc
···11+# Expo doesn't play nice with pnpm by default.
22+# The symbolic links of pnpm break the rules of Expo monorepos.
33+# @link https://docs.expo.dev/guides/monorepos/#common-issues
44+node-linker=hoisted
55+66+# In order to cache Prisma correctly
77+public-hoist-pattern[]=*prisma*
88+99+# FIXME: @prisma/client is required by the @acme/auth,
1010+# but we don't want it installed there since it's already
1111+# installed in the @acme/db package
1212+strict-peer-dependencies=false
1313+1414+# Prevent pnpm from adding the "workspace:"" prefix to local
1515+# packages as it casues issues with manypkg
1616+# @link https://pnpm.io/npmrc#prefer-workspace-packages
1717+save-workspace-protocol=false
1818+prefer-workspace-packages=true
···11+MIT License
22+33+Copyright (c) 2023 Julius Marminge
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+208
README.md
···11+# create-t3-turbo
22+33+<img width="1758" alt="turbo2" src="https://user-images.githubusercontent.com/51714798/213819392-33e50db9-3e38-4c51-9a22-03abe5e48f3d.png">
44+55+## Installation
66+77+There are two ways of initializing an app using `create-t3-turbo` starter. You can either use this repository as a template or use Turbo's CLI to init your project:
88+```bash
99+npx create-turbo@latest -e https://github.com/t3-oss/create-t3-turbo
1010+```
1111+1212+## About
1313+1414+Ever wondered how to migrate your T3 application into a monorepo? Stop right here! This is the perfect starter repo to get you running with the perfect stack!
1515+1616+It uses [Turborepo](https://turborepo.org/) and contains:
1717+1818+```
1919+.github
2020+ └─ workflows
2121+ └─ CI with pnpm cache setup
2222+.vscode
2323+ └─ Recommended extensions and settings for VSCode users
2424+apps
2525+ ├─ expo
2626+ | ├─ Expo SDK 48
2727+ | ├─ React Native using React 18
2828+ | ├─ Navigation using Expo Router
2929+ | ├─ Tailwind using Nativewind
3030+ | └─ Typesafe API calls using tRPC
3131+ └─ next.js
3232+ ├─ Next.js 13
3333+ ├─ React 18
3434+ ├─ Tailwind CSS
3535+ └─ E2E Typesafe API Server & Client
3636+packages
3737+ ├─ api
3838+ | └─ tRPC v10 router definition
3939+ ├─ auth
4040+ └─ authentication using next-auth. **NOTE: Only for Next.js app, not Expo**
4141+ └─ db
4242+ └─ typesafe db-calls using Prisma
4343+```
4444+4545+## FAQ
4646+4747+### Can you include Solito?
4848+4949+No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo - it's the codesplitting of your T3 App into a monorepo, the Expo app is just a bonus example of how you can utilize the monorepo with multiple apps but can just as well be any app such as Vite, Electron, etc.
5050+5151+Integrating Solito into this repo isn't hard, and there are a few [offical templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference.
5252+5353+### What auth solution should I use instead of Next-Auth.js for Expo?
5454+5555+I've left this kind of open for you to decide. Some options are [Clerk](https://clerk.dev), [Supabase Auth](https://supabase.com/docs/guides/auth), [Firebase Auth](https://firebase.google.com/docs/auth/) or [Auth0](https://auth0.com/docs). Note that if you're dropping the Expo app for something more "browser-like", you can still use Next-Auth.js for those. [See an example in a Plasmo Chrome Extension here](https://github.com/t3-oss/create-t3-turbo/tree/chrome/apps/chrome).
5656+5757+The Clerk.dev team even made an [official template repository](https://github.com/clerkinc/t3-turbo-and-clerk) integrating Clerk.dev with this repo.
5858+5959+### Does this pattern leak backend code to my client applications?
6060+6161+No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, and all other apps you may add in the future, should only add the `api` package as a dev dependency. This lets you have full typesafety in your client applications, while keeping your backend code safe.
6262+6363+If you need to share runtime code between the client and server, such as input validation schemas, you can create a separate `shared` package for this and import on both sides.
6464+6565+## Quick Start
6666+6767+To get it running, follow the steps below:
6868+6969+### Setup dependencies
7070+7171+```diff
7272+# Install dependencies
7373+pnpm i
7474+7575+# In packages/db/prisma update schema.prisma provider to use sqlite
7676+# or use your own database provider
7777+- provider = "postgresql"
7878++ provider = "sqlite"
7979+8080+# Configure environment variables.
8181+# There is an `.env.example` in the root directory you can use for reference
8282+cp .env.example .env
8383+8484+# Push the Prisma schema to your database
8585+pnpm db:push
8686+```
8787+8888+### Configure Expo `dev`-script
8989+9090+#### Use iOS Simulator
9191+9292+1. Make sure you have XCode and XCommand Line Tools installed [as shown on expo docs](https://docs.expo.dev/workflow/ios-simulator/).
9393+ > **NOTE:** If you just installed XCode, or if you have updated it, you need to open the simulator manually once. Run `npx expo start` in the root dir, and then enter `I` to launch Expo Go. After the manual launch, you can run `pnpm dev` in the root directory.
9494+9595+```diff
9696++ "dev": "expo start --ios",
9797+```
9898+9999+3. Run `pnpm dev` at the project root folder.
100100+101101+> **TIP:** It might be easier to run each app in separate terminal windows so you get the logs from each app separately. This is also required if you want your terminals to be interactive, e.g. to access the Expo QR code. You can run `pnpm --filter expo dev` and `pnpm --filter nextjs dev` to run each app in a separate terminal window.
102102+103103+#### For Android
104104+105105+1. Install Android Studio tools [as shown on expo docs](https://docs.expo.dev/workflow/android-studio-emulator/).
106106+2. Change the `dev` script at `apps/expo/package.json` to open the Android emulator.
107107+108108+```diff
109109++ "dev": "expo start --android",
110110+```
111111+112112+3. Run `pnpm dev` at the project root folder.
113113+114114+## Deployment
115115+116116+### Next.js
117117+118118+#### Prerequisites
119119+120120+_We do not recommend deploying a SQLite database on serverless environments since the data wouldn't be persisted. I provisioned a quick Postgresql database on [Railway](https://railway.app), but you can of course use any other database provider. Make sure the prisma schema is updated to use the correct database._
121121+122122+**Please note that the Next.js application with tRPC must be deployed in order for the Expo app to communicate with the server in a production environment.**
123123+124124+#### Deploy to Vercel
125125+126126+Let's deploy the Next.js application to [Vercel](https://vercel.com/). If you have ever deployed a Turborepo app there, the steps are quite straightforward. You can also read the [official Turborepo guide](https://vercel.com/docs/concepts/monorepos/turborepo) on deploying to Vercel.
127127+128128+1. Create a new project on Vercel, select the `apps/nextjs` folder as the root directory and apply the following build settings:
129129+130130+<img width="927" alt="Vercel deployment settings" src="https://user-images.githubusercontent.com/11340449/201974887-b6403a32-5570-4ce6-b146-c486c0dbd244.png">
131131+132132+> The install command filters out the expo package and saves a few second (and cache size) of dependency installation. The build command makes us build the application using Turbo.
133133+134134+2. Add your `DATABASE_URL` environment variable.
135135+136136+3. Done! Your app should successfully deploy. Assign your domain and use that instead of `localhost` for the `url` in the Expo app so that your Expo app can communicate with your backend when you are not in development.
137137+138138+### Expo
139139+140140+Deploying your Expo application works slightly differently compared to Next.js on the web. Instead of "deploying" your app online, you need to submit production builds of your app to the app stores, like [Apple App Store](https://www.apple.com/app-store/) and [Google Play](https://play.google.com/store/apps). You can read the full [Distributing your app](https://docs.expo.dev/distribution/introduction/), including best practices, in the Expo docs.
141141+142142+1. Make sure to modify the `getBaseUrl` function to point to your backend's production URL:
143143+144144+https://github.com/t3-oss/create-t3-turbo/blob/656965aff7db271e5e080242c4a3ce4dad5d25f8/apps/expo/src/utils/api.tsx#L20-L37
145145+146146+2. Let's start by setting up [EAS Build](https://docs.expo.dev/build/introduction/), which is short for Expo Application Services. The build service helps you create builds of your app, without requiring a full native development setup. The commands below are a summary of [Creating your first build](https://docs.expo.dev/build/setup/).
147147+148148+ ```bash
149149+ // Install the EAS CLI
150150+ $ pnpm add -g eas-cli
151151+152152+ // Log in with your Expo account
153153+ $ eas login
154154+155155+ // Configure your Expo app
156156+ $ cd apps/expo
157157+ $ eas build:configure
158158+ ```
159159+160160+3. After the initial setup, you can create your first build. You can build for Android and iOS platforms and use different [**eas.json** build profiles](https://docs.expo.dev/build-reference/eas-json/) to create production builds or development, or test builds. Let's make a production build for iOS.
161161+162162+ ```
163163+ $ eas build --platform ios --profile production
164164+ ```
165165+166166+ > If you don't specify the `--profile` flag, EAS uses the `production` profile by default.
167167+168168+4. Now that you have your first production build, you can submit this to the stores. [EAS Submit](https://docs.expo.dev/submit/introduction/) can help you send the build to the stores.
169169+170170+ ```
171171+ $ eas submit --platform ios --latest
172172+ ```
173173+174174+ > You can also combine build and submit in a single command, using `eas build ... --auto-submit`.
175175+176176+5. Before you can get your app in the hands of your users, you'll have to provide additional information to the app stores. This includes screenshots, app information, privacy policies, etc. _While still in preview_, [EAS Metadata](https://docs.expo.dev/eas/metadata/) can help you with most of this information.
177177+178178+6. Once everything is approved, your users can finally enjoy your app. Let's say you spotted a small typo; you'll have to create a new build, submit it to the stores, and wait for approval before you can resolve this issue. In these cases, you can use EAS Update to quickly send a small bugfix to your users without going through this long process. Let's start by setting up EAS Update.
179179+180180+ The steps below summarize the [Getting started with EAS Update](https://docs.expo.dev/eas-update/getting-started/#configure-your-project) guide.
181181+182182+ ```bash
183183+ // Add the `expo-updates` library to your Expo app
184184+ $ cd apps/expo
185185+ $ pnpm expo install expo-updates
186186+187187+ // Configure EAS Update
188188+ $ eas update:configure
189189+ ```
190190+191191+7. Before we can send out updates to your app, you have to create a new build and submit it to the app stores. For every change that includes native APIs, you have to rebuild the app and submit the update to the app stores. See steps 2 and 3.
192192+193193+8. Now that everything is ready for updates, let's create a new update for `production` builds. With the `--auto` flag, EAS Update uses your current git branch name and commit message for this update. See [How EAS Update works](https://docs.expo.dev/eas-update/how-eas-update-works/#publishing-an-update) for more information.
194194+195195+ ```bash
196196+ $ cd apps/expo
197197+ $ eas update --auto
198198+ ```
199199+200200+ > Your OTA (Over The Air) updates must always follow the app store's rules. You can't change your app's primary functionality without getting app store approval. But this is a fast way to update your app for minor changes and bug fixes.
201201+202202+9. Done! Now that you have created your production build, submitted it to the stores, and installed EAS Update, you are ready for anything!
203203+204204+## References
205205+206206+The stack originates from [create-t3-app](https://github.com/t3-oss/create-t3-app).
207207+208208+A [blog post](https://jumr.dev/blog/t3-turbo) where I wrote how to migrate a T3 app into this.
···11+import { registerRootComponent } from "expo";
22+import { ExpoRoot } from "expo-router";
33+44+// Must be exported or Fast Refresh won't update the context
55+export function App() {
66+ const ctx = require.context("./src/app");
77+ return <ExpoRoot context={ctx} />;
88+}
99+1010+registerRootComponent(App);
+24
apps/expo/metro.config.js
···11+// Learn more: https://docs.expo.dev/guides/monorepos/
22+const { getDefaultConfig } = require("@expo/metro-config");
33+const path = require("path");
44+55+const projectRoot = __dirname;
66+const workspaceRoot = path.resolve(projectRoot, "../..");
77+88+// Create the default Metro config
99+const config = getDefaultConfig(projectRoot);
1010+1111+// Add the additional `cjs` extension to the resolver
1212+config.resolver.sourceExts.push("cjs");
1313+1414+// 1. Watch all files within the monorepo
1515+config.watchFolders = [workspaceRoot];
1616+// 2. Let Metro know where to resolve packages and in what order
1717+config.resolver.nodeModulesPaths = [
1818+ path.resolve(projectRoot, "node_modules"),
1919+ path.resolve(workspaceRoot, "node_modules"),
2020+];
2121+// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
2222+// config.resolver.disableHierarchicalLookup = true;
2323+2424+module.exports = config;
···11+import React from "react";
22+import { SafeAreaProvider } from "react-native-safe-area-context";
33+import { Stack } from "expo-router";
44+import { StatusBar } from "expo-status-bar";
55+66+import { TRPCProvider } from "../utils/api";
77+88+// This is the main layout of the app
99+// It wraps your pages with the providers they need
1010+const RootLayout = () => {
1111+ return (
1212+ <TRPCProvider>
1313+ <SafeAreaProvider>
1414+ {/*
1515+ The Stack component displays the current page.
1616+ It also allows you to configure your screens
1717+ */}
1818+ <Stack
1919+ screenOptions={{
2020+ headerStyle: {
2121+ backgroundColor: "#f472b6",
2222+ },
2323+ }}
2424+ />
2525+ <StatusBar />
2626+ </SafeAreaProvider>
2727+ </TRPCProvider>
2828+ );
2929+};
3030+3131+export default RootLayout;
···11+import { SafeAreaView, Text, View } from "react-native";
22+import { SplashScreen, Stack, useSearchParams } from "expo-router";
33+44+import { api } from "../../utils/api";
55+66+const Post: React.FC = () => {
77+ const { id } = useSearchParams();
88+ if (!id || typeof id !== "string") throw new Error("unreachable");
99+ const { data } = api.post.byId.useQuery({ id });
1010+1111+ if (!data) return <SplashScreen />;
1212+1313+ return (
1414+ <SafeAreaView className="bg-[#1F104A]">
1515+ <Stack.Screen options={{ title: data.title }} />
1616+ <View className="h-full w-full p-4">
1717+ <Text className="py-2 text-3xl font-bold text-white">{data.title}</Text>
1818+ <Text className="py-4 text-white">{data.content}</Text>
1919+ </View>
2020+ </SafeAreaView>
2121+ );
2222+};
2323+2424+export default Post;
+63
apps/expo/src/utils/api.tsx
···11+import React from "react";
22+import Constants from "expo-constants";
33+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
44+import { httpBatchLink } from "@trpc/client";
55+import { createTRPCReact } from "@trpc/react-query";
66+import superjson from "superjson";
77+88+import { type AppRouter } from "@acme/api";
99+1010+/**
1111+ * A set of typesafe hooks for consuming your API.
1212+ */
1313+export const api = createTRPCReact<AppRouter>();
1414+export { type RouterInputs, type RouterOutputs } from "@acme/api";
1515+1616+/**
1717+ * Extend this function when going to production by
1818+ * setting the baseUrl to your production API URL.
1919+ */
2020+const getBaseUrl = () => {
2121+ /**
2222+ * Gets the IP address of your host-machine. If it cannot automatically find it,
2323+ * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm
2424+ * you don't have anything else running on it, or you'd have to change it.
2525+ *
2626+ * **NOTE**: This is only for development. In production, you'll want to set the
2727+ * baseUrl to your production API URL.
2828+ */
2929+ const localhost = Constants.manifest?.debuggerHost?.split(":")[0];
3030+ if (!localhost) {
3131+ // return "https://your-production-url.com";
3232+ throw new Error(
3333+ "Failed to get localhost. Please point to your production server.",
3434+ );
3535+ }
3636+ return `http://${localhost}:3000`;
3737+};
3838+3939+/**
4040+ * A wrapper for your app that provides the TRPC context.
4141+ * Use only in _app.tsx
4242+ */
4343+export const TRPCProvider: React.FC<{ children: React.ReactNode }> = ({
4444+ children,
4545+}) => {
4646+ const [queryClient] = React.useState(() => new QueryClient());
4747+ const [trpcClient] = React.useState(() =>
4848+ api.createClient({
4949+ transformer: superjson,
5050+ links: [
5151+ httpBatchLink({
5252+ url: `${getBaseUrl()}/api/trpc`,
5353+ }),
5454+ ],
5555+ }),
5656+ );
5757+5858+ return (
5959+ <api.Provider client={trpcClient} queryClient={queryClient}>
6060+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
6161+ </api.Provider>
6262+ );
6363+};
+16
apps/expo/tailwind.config.js
···11+// TODO: Add support for TS config files in Nativewind.
22+33+// import { type Config } from "tailwindcss";
44+55+// import baseConfig from "@acme/tailwind-config";
66+77+// export default {
88+// presets: [baseConfig],
99+// content: ["./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"],
1010+// } satisfies Config;
1111+1212+const config = {
1313+ content: ["./src/**/*.{ts,tsx}"],
1414+};
1515+1616+module.exports = config;
···11+# Create T3 App
22+33+This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
44+55+## What's next? How do I make an app with this?
66+77+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.
88+99+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.
1010+1111+- [Next.js](https://nextjs.org)
1212+- [NextAuth.js](https://next-auth.js.org)
1313+- [Prisma](https://prisma.io)
1414+- [Tailwind CSS](https://tailwindcss.com)
1515+- [tRPC](https://trpc.io)
1616+1717+## Learn More
1818+1919+To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
2020+2121+- [Documentation](https://create.t3.gg/)
2222+- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
2323+2424+You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
2525+2626+## How do I deploy this?
2727+2828+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.
+17
apps/nextjs/next.config.mjs
···11+/**
22+ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
33+ * This is especially useful for Docker builds and Linting.
44+ */
55+// !process.env.SKIP_ENV_VALIDATION && (await import("./src/env.mjs"));
66+77+/** @type {import("next").NextConfig} */
88+const config = {
99+ reactStrictMode: true,
1010+ /** Enables hot reloading for local packages without a build step */
1111+ transpilePackages: ["@acme/api", "@acme/auth", "@acme/db"],
1212+ /** We already do linting and typechecking as separate tasks in CI */
1313+ eslint: { ignoreDuringBuilds: !!process.env.CI },
1414+ typescript: { ignoreBuildErrors: !!process.env.CI },
1515+};
1616+1717+export default config;
···11+import { z } from "zod";
22+33+/**
44+ * Specify your server-side environment variables schema here. This way you can ensure the app isn't
55+ * built with invalid env vars.
66+ */
77+const server = z.object({
88+ DATABASE_URL: z.string().url(),
99+ NODE_ENV: z.enum(["development", "test", "production"]),
1010+ NEXTAUTH_SECRET:
1111+ process.env.NODE_ENV === "production"
1212+ ? z.string().min(1)
1313+ : z.string().min(1).optional(),
1414+ NEXTAUTH_URL: z.preprocess(
1515+ // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
1616+ // Since NextAuth.js automatically uses the VERCEL_URL if present.
1717+ (str) => process.env.VERCEL_URL ?? str,
1818+ // VERCEL_URL doesn't include `https` so it cant be validated as a URL
1919+ process.env.VERCEL ? z.string() : z.string().url(),
2020+ ),
2121+ // Add `.min(1) on ID and SECRET if you want to make sure they're not empty
2222+ DISCORD_CLIENT_ID: z.string(),
2323+ DISCORD_CLIENT_SECRET: z.string(),
2424+});
2525+2626+/**
2727+ * Specify your client-side environment variables schema here. This way you can ensure the app isn't
2828+ * built with invalid env vars. To expose them to the client, prefix them with `NEXT_PUBLIC_`.
2929+ */
3030+const client = z.object({
3131+ // NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
3232+});
3333+3434+/**
3535+ * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
3636+ * middlewares) or client-side so we need to destruct manually.
3737+ *
3838+ * @type {Record<keyof z.infer<typeof server> | keyof z.infer<typeof client>, string | undefined>}
3939+ */
4040+const processEnv = {
4141+ DATABASE_URL: process.env.DATABASE_URL,
4242+ NODE_ENV: process.env.NODE_ENV,
4343+ NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
4444+ NEXTAUTH_URL: process.env.NEXTAUTH_URL,
4545+ DISCORD_CLIENT_ID: process.env.DISCORD_CLIENT_ID,
4646+ DISCORD_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET,
4747+ // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
4848+};
4949+5050+// Don't touch the part below
5151+// --------------------------
5252+5353+const merged = server.merge(client);
5454+5555+/** @typedef {z.input<typeof merged>} MergedInput */
5656+/** @typedef {z.infer<typeof merged>} MergedOutput */
5757+/** @typedef {z.SafeParseReturnType<MergedInput, MergedOutput>} MergedSafeParseReturn */
5858+5959+let env = /** @type {MergedOutput} */ (process.env);
6060+6161+if (!!process.env.SKIP_ENV_VALIDATION == false) {
6262+ const isServer = typeof window === "undefined";
6363+6464+ const parsed = /** @type {MergedSafeParseReturn} */ (
6565+ isServer
6666+ ? merged.safeParse(processEnv) // on server we can validate all env vars
6767+ : client.safeParse(processEnv) // on client we can only validate the ones that are exposed
6868+ );
6969+7070+ if (parsed.success === false) {
7171+ console.error(
7272+ "❌ Invalid environment variables:",
7373+ parsed.error.flatten().fieldErrors,
7474+ );
7575+ throw new Error("Invalid environment variables");
7676+ }
7777+7878+ env = new Proxy(parsed.data, {
7979+ get(target, prop) {
8080+ if (typeof prop !== "string") return undefined;
8181+ // Throw a descriptive error if a server-side env var is accessed on the client
8282+ // Otherwise it would just be returning `undefined` and be annoying to debug
8383+ if (!isServer && !prop.startsWith("NEXT_PUBLIC_"))
8484+ throw new Error(
8585+ process.env.NODE_ENV === "production"
8686+ ? "❌ Attempted to access a server-side environment variable on the client"
8787+ : `❌ Attempted to access server-side environment variable '${prop}' on the client`,
8888+ );
8989+ return target[/** @type {keyof typeof target} */ (prop)];
9090+ },
9191+ });
9292+}
9393+9494+export { env };
+19
apps/nextjs/src/pages/_app.tsx
···11+import "../styles/globals.css";
22+import type { AppType } from "next/app";
33+import type { Session } from "next-auth";
44+import { SessionProvider } from "next-auth/react";
55+66+import { api } from "~/utils/api";
77+88+const MyApp: AppType<{ session: Session | null }> = ({
99+ Component,
1010+ pageProps: { session, ...pageProps },
1111+}) => {
1212+ return (
1313+ <SessionProvider session={session}>
1414+ <Component {...pageProps} />
1515+ </SessionProvider>
1616+ );
1717+};
1818+1919+export default api.withTRPC(MyApp);
+5
apps/nextjs/src/pages/api/auth/[...nextauth].ts
···11+import NextAuth from "next-auth";
22+33+import { authOptions } from "@acme/auth";
44+55+export default NextAuth(authOptions);
+23
apps/nextjs/src/pages/api/trpc/[trpc].ts
···11+import { createNextApiHandler } from "@trpc/server/adapters/next";
22+33+import { appRouter, createTRPCContext } from "@acme/api";
44+55+// export API handler
66+export default createNextApiHandler({
77+ router: appRouter,
88+ createContext: createTRPCContext,
99+});
1010+1111+// If you need to enable cors, you can do so like this:
1212+// const handler = async (req: NextApiRequest, res: NextApiResponse) => {
1313+// // Enable cors
1414+// await cors(req, res);
1515+1616+// // Let the tRPC handler do its magic
1717+// return createNextApiHandler({
1818+// router: appRouter,
1919+// createContext,
2020+// })(req, res);
2121+// };
2222+2323+// export default handler;
···11+/**
22+ * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
33+ * 1. You want to modify request context (see Part 1)
44+ * 2. You want to create a new middleware or type of procedure (see Part 3)
55+ *
66+ * tl;dr - this is where all the tRPC server stuff is created and plugged in.
77+ * The pieces you will need to use are documented accordingly near the end
88+ */
99+import { TRPCError, initTRPC } from "@trpc/server";
1010+import { type CreateNextContextOptions } from "@trpc/server/adapters/next";
1111+import superjson from "superjson";
1212+import { ZodError } from "zod";
1313+1414+import { getServerSession, type Session } from "@acme/auth";
1515+import { prisma } from "@acme/db";
1616+1717+/**
1818+ * 1. CONTEXT
1919+ *
2020+ * This section defines the "contexts" that are available in the backend API
2121+ *
2222+ * These allow you to access things like the database, the session, etc, when
2323+ * processing a request
2424+ *
2525+ */
2626+type CreateContextOptions = {
2727+ session: Session | null;
2828+};
2929+3030+/**
3131+ * This helper generates the "internals" for a tRPC context. If you need to use
3232+ * it, you can export it from here
3333+ *
3434+ * Examples of things you may need it for:
3535+ * - testing, so we dont have to mock Next.js' req/res
3636+ * - trpc's `createSSGHelpers` where we don't have req/res
3737+ * @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts
3838+ */
3939+const createInnerTRPCContext = (opts: CreateContextOptions) => {
4040+ return {
4141+ session: opts.session,
4242+ prisma,
4343+ };
4444+};
4545+4646+/**
4747+ * This is the actual context you'll use in your router. It will be used to
4848+ * process every request that goes through your tRPC endpoint
4949+ * @link https://trpc.io/docs/context
5050+ */
5151+export const createTRPCContext = async (opts: CreateNextContextOptions) => {
5252+ const { req, res } = opts;
5353+5454+ // Get the session from the server using the unstable_getServerSession wrapper function
5555+ const session = await getServerSession({ req, res });
5656+5757+ return createInnerTRPCContext({
5858+ session,
5959+ });
6060+};
6161+6262+/**
6363+ * 2. INITIALIZATION
6464+ *
6565+ * This is where the trpc api is initialized, connecting the context and
6666+ * transformer
6767+ */
6868+const t = initTRPC.context<typeof createTRPCContext>().create({
6969+ transformer: superjson,
7070+ errorFormatter({ shape, error }) {
7171+ return {
7272+ ...shape,
7373+ data: {
7474+ ...shape.data,
7575+ zodError:
7676+ error.cause instanceof ZodError ? error.cause.flatten() : null,
7777+ },
7878+ };
7979+ },
8080+});
8181+8282+/**
8383+ * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
8484+ *
8585+ * These are the pieces you use to build your tRPC API. You should import these
8686+ * a lot in the /src/server/api/routers folder
8787+ */
8888+8989+/**
9090+ * This is how you create new routers and subrouters in your tRPC API
9191+ * @see https://trpc.io/docs/router
9292+ */
9393+export const createTRPCRouter = t.router;
9494+9595+/**
9696+ * Public (unauthed) procedure
9797+ *
9898+ * This is the base piece you use to build new queries and mutations on your
9999+ * tRPC API. It does not guarantee that a user querying is authorized, but you
100100+ * can still access user session data if they are logged in
101101+ */
102102+export const publicProcedure = t.procedure;
103103+104104+/**
105105+ * Reusable middleware that enforces users are logged in before running the
106106+ * procedure
107107+ */
108108+const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
109109+ if (!ctx.session?.user) {
110110+ throw new TRPCError({ code: "UNAUTHORIZED" });
111111+ }
112112+ return next({
113113+ ctx: {
114114+ // infers the `session` as non-nullable
115115+ session: { ...ctx.session, user: ctx.session.user },
116116+ },
117117+ });
118118+});
119119+120120+/**
121121+ * Protected (authed) procedure
122122+ *
123123+ * If you want a query or mutation to ONLY be accessible to logged in users, use
124124+ * this. It verifies the session is valid and guarantees ctx.session.user is not
125125+ * null
126126+ *
127127+ * @see https://trpc.io/docs/procedures
128128+ */
129129+export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
···11+export { authOptions } from "./src/auth-options";
22+export { getServerSession } from "./src/get-session";
33+export type { Session } from "next-auth";
···11+import { PrismaAdapter } from "@next-auth/prisma-adapter";
22+import { type DefaultSession, type NextAuthOptions } from "next-auth";
33+import DiscordProvider from "next-auth/providers/discord";
44+55+import { prisma } from "@acme/db";
66+77+/**
88+ * Module augmentation for `next-auth` types
99+ * Allows us to add custom properties to the `session` object
1010+ * and keep type safety
1111+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
1212+ **/
1313+declare module "next-auth" {
1414+ interface Session extends DefaultSession {
1515+ user: {
1616+ id: string;
1717+ // ...other properties
1818+ // role: UserRole;
1919+ } & DefaultSession["user"];
2020+ }
2121+2222+ // interface User {
2323+ // // ...other properties
2424+ // // role: UserRole;
2525+ // }
2626+}
2727+2828+/**
2929+ * Options for NextAuth.js used to configure
3030+ * adapters, providers, callbacks, etc.
3131+ * @see https://next-auth.js.org/configuration/options
3232+ **/
3333+export const authOptions: NextAuthOptions = {
3434+ callbacks: {
3535+ session({ session, user }) {
3636+ if (session.user) {
3737+ session.user.id = user.id;
3838+ // session.user.role = user.role; <-- put other properties on the session here
3939+ }
4040+ return session;
4141+ },
4242+ },
4343+ adapter: PrismaAdapter(prisma),
4444+ providers: [
4545+ DiscordProvider({
4646+ clientId: process.env.DISCORD_CLIENT_ID as string,
4747+ clientSecret: process.env.DISCORD_CLIENT_SECRET as string,
4848+ }),
4949+ /**
5050+ * ...add more providers here
5151+ *
5252+ * Most other providers require a bit more work than the Discord provider.
5353+ * For example, the GitHub provider requires you to add the
5454+ * `refresh_token_expires_in` field to the Account model. Refer to the
5555+ * NextAuth.js docs for the provider you want to use. Example:
5656+ * @see https://next-auth.js.org/providers/github
5757+ **/
5858+ ],
5959+};