├── .env ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ └── lint.yml ├── .gitignore ├── README.md ├── apps ├── expo │ ├── .babelrc.js │ ├── .env.example │ ├── .expo-shared │ │ └── assets.json │ ├── .gitignore │ ├── App.tsx │ ├── app.config.js │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── icon.development.png │ │ ├── icon.png │ │ ├── icon.staging.png │ │ └── splash.png │ ├── eas.json │ ├── index.js │ ├── metro.config.js │ ├── package.json │ ├── react-native.config.js │ ├── tsconfig.json │ └── utils │ │ └── trpc.tsx └── nextjs │ ├── .env │ ├── .gitignore │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── jest-playwright.config.js │ ├── jest.config.js │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── public │ ├── favicon.ico │ └── vercel.svg │ ├── src │ ├── pages │ │ ├── _app.tsx │ │ ├── api │ │ │ └── trpc │ │ │ │ └── [trpc].ts │ │ ├── index.tsx │ │ └── post │ │ │ └── [id].tsx │ └── utils │ │ └── trpc.ts │ ├── test │ └── playwright.test.ts │ └── tsconfig.json ├── docker-compose.yml ├── package.json ├── packages ├── api │ ├── package.json │ └── src │ │ ├── createContext.ts │ │ ├── createRouter.ts │ │ └── routers │ │ ├── _app.ts │ │ └── post.ts ├── react-native │ ├── hello-world.tsx │ ├── package.json │ ├── theme.ts │ └── trpc.tsx └── react │ ├── package.json │ └── trpc.tsx ├── prisma ├── migrations │ ├── 20210910132122_init │ │ └── migration.sql │ ├── 20220111230540_prisma_3 │ │ └── migration.sql │ ├── 20220201105846_drop_unique │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── tsconfig.base.json ├── tsconfig.json └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#using-environment-variables 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL and SQLite. 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL="postgresql://postgres:@localhost:5466/zart" 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", // Specifies the ESLint parser 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | "plugin:react/recommended", 6 | "plugin:react-hooks/recommended", 7 | "plugin:prettier/recommended" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 2018, // Allows for the parsing of modern ECMAScript features 11 | "sourceType": "module" // Allows for the use of imports 12 | }, 13 | "rules": { 14 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 15 | "@typescript-eslint/explicit-function-return-type": "off", 16 | "@typescript-eslint/explicit-module-boundary-types": "off", 17 | "react/react-in-jsx-scope": "off", 18 | "react/prop-types": "off", 19 | "@typescript-eslint/no-explicit-any": "off" 20 | }, 21 | "overrides": [ 22 | { 23 | "files": ["examples/**/*"], 24 | "rules": { 25 | "@typescript-eslint/no-unused-vars": "off" 26 | } 27 | } 28 | ], 29 | "settings": { 30 | "react": { 31 | "version": "detect" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: KATT 2 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: [push] 3 | jobs: 4 | lint: 5 | runs-on: ubuntu-latest 6 | 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | 12 | - name: Use Node.js 14.x 13 | uses: actions/setup-node@v1 14 | with: 15 | version: 14.x 16 | 17 | - name: Install deps 18 | uses: bahmutov/npm-install@v1 19 | 20 | - name: Lint 21 | run: yarn lint 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | 6 | node_modules 7 | package-lock.json 8 | 9 | .yalc 10 | yalc.lock 11 | 12 | coverage/ 13 | *.db 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ℹ️ℹ️ℹ️ This example project is not actively maintained and is using an old version (v9) of tRPC ℹ️ℹ️ℹ️ 2 | 3 | If you're looking for alternatives, have a look at https://github.com/t3-oss/create-t3-turbo and other reference projects on https://trpc.io/docs/awesome-trpc 4 | 5 | 6 | --- 7 | 8 | 9 | # zART-Stack 🤯 10 | 11 | > **Z**ero-**A**PI, **R**eact, & **T**ypeScript 12 | 13 | **⚡️ Probably the fastest way to build a React Native app with your own backend ⚡️** 14 | 15 | ## Introduction 16 | 17 | A monorepo containing: 18 | 19 | - Next.js web app 20 | - React Native app with Expo 21 | - A [tRPC](https://trpc.io)-API which is inferred straight into the above. 22 | - [Prisma](http://prisma.io/) as a typesafe ORM 23 | 24 | > In tRPC you simply write API-functions that are automatically inferred straight into your frontend - no matter if it's React, React Native, or something else _(that is TS/JS-based)_. 25 | 26 | ### Video 27 | 28 | > Very rough video recorded in 2 minutes 😅 29 | 30 | [![ZART](http://img.youtube.com/vi/dLLm6hgMhMQ/0.jpg)](http://www.youtube.com/watch?v=dLLm6hgMhMQ "Video Title") 31 | 32 | ## Requirements 33 | 34 | You will need docker compose to run the postgres database. 35 | It comes with the [Docker Desktop App](https://docs.docker.com/get-docker/) 36 | 37 | ## Getting started 38 | 39 | ```bash 40 | git clone git@github.com:KATT/zart.git 41 | cd zart 42 | yarn 43 | yarn dev 44 | ``` 45 | 46 | > Press `i` after `yarn dev` in to launch the iOS Simulator. 47 | 48 | Now - head over to one of the [`./apps`](./apps), whilist updating [a router in tRPC](./packages/api/src/routers) or the [Database Schema](./prisma/schema.prisma) and see that the data is directly inferred. 49 | 50 | ## Available commands 51 | 52 | | Command | Description | 53 | | --------------------- | ---------------------------------------------------------------------------------------------- | 54 | | `yarn dev` | Starts Postgres, Expo & Next.js | 55 | | `yarn db-up` | Starts Postgres on port `5466` | 56 | | `yarn db-migrate-dev` | Runs the latest Database migrations after updating the [Prisma schema](./prisma/schema.prisma) | 57 | | `yarn db-nuke` | Stops and deletes the the database | 58 | 59 | 60 | ## Folder structure 61 | 62 | 63 | ```graphql 64 | . 65 | ├── apps 66 | │ ├── expo # Expo/RN application 67 | │ └── nextjs # Server-side rendered Next.js application 68 | ├── packages 69 | │ ├── api # tRPC API 70 | │ ├── react # Shared React-helpers 71 | │ └── react-native # RN components. **Could** be shared between Expo & Next.js if you're in to that sort of thing. 72 | └── prisma # Prisma setup 73 | ``` 74 | 75 | ## Further reading 76 | 77 | ### Deployment 78 | 79 | #### Vercel 80 | 81 | - Create a Postgres Database 82 | - Set env `DATABASE_URL` pointing towards that db 83 | - Configure *"Root Directory"* as `apps/nextjs` and tick _Include source files outside of the Root Directory in the Build Step_. 84 | 85 | 86 | ### Questions? 87 | 88 | Shoot me a message [on Twitter](https://twitter.com/alexdotjs)! 89 | 90 | 91 | ## Credits 92 | 93 | - tRPC and this example is made by [@alexdotjs](https://twitter.com/alexdotjs) 94 | - `apps/expo` is basically a copy-paste from [`expo-next-monorepo-example`](https://github.com/axeldelafosse/expo-next-monorepo-example) by [axeldelafosse](https://github.com/axeldelafosse). 95 | 96 | -------------------------------------------------------------------------------- /apps/expo/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | 4 | return { 5 | presets: ['@expo/next-adapter/babel'], 6 | plugins: ['react-native-reanimated/plugin'] 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /apps/expo/.env.example: -------------------------------------------------------------------------------- 1 | STAGE=development -------------------------------------------------------------------------------- /apps/expo/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /apps/expo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | .next/* 4 | npm-debug.* 5 | yarn-error.* 6 | *.jks 7 | *.p8 8 | *.p12 9 | *.key 10 | *.mobileprovision 11 | *.orig.* 12 | web-build/ 13 | __generated__/ 14 | # @generated expo-cli sync-2138f1e3e130677ea10ea873f6d498e3890e677b 15 | # The following patterns were generated by expo-cli 16 | 17 | # OSX 18 | # 19 | .DS_Store 20 | 21 | # Xcode 22 | # 23 | build/ 24 | *.pbxuser 25 | !default.pbxuser 26 | *.mode1v3 27 | !default.mode1v3 28 | *.mode2v3 29 | !default.mode2v3 30 | *.perspectivev3 31 | !default.perspectivev3 32 | xcuserdata 33 | *.xccheckout 34 | *.moved-aside 35 | DerivedData 36 | *.hmap 37 | *.ipa 38 | *.xcuserstate 39 | project.xcworkspace 40 | 41 | # Android/IntelliJ 42 | # 43 | build/ 44 | .idea 45 | .gradle 46 | local.properties 47 | *.iml 48 | *.hprof 49 | 50 | # node.js 51 | # 52 | node_modules/ 53 | npm-debug.log 54 | yarn-error.log 55 | 56 | # BUCK 57 | buck-out/ 58 | \.buckd/ 59 | *.keystore 60 | !debug.keystore 61 | 62 | # Bundle artifacts 63 | *.jsbundle 64 | 65 | # CocoaPods 66 | /ios/Pods/ 67 | 68 | # Expo 69 | .expo/ 70 | web-build/ 71 | 72 | # @end expo-cli 73 | 74 | # Native 75 | ios/ 76 | android/ -------------------------------------------------------------------------------- /apps/expo/App.tsx: -------------------------------------------------------------------------------- 1 | import { HelloWorld } from '@zart/react-native/hello-world'; 2 | import { theme } from '@zart/react-native/theme'; 3 | import { DripsyProvider } from 'dripsy'; 4 | import Constants from 'expo-constants'; 5 | import { StatusBar } from 'expo-status-bar'; 6 | import React, { useState } from 'react'; 7 | import { SafeAreaProvider } from 'react-native-safe-area-context'; 8 | import { enableScreens } from 'react-native-screens'; 9 | import { QueryClient, QueryClientProvider } from 'react-query'; 10 | import { transformer, trpc } from './utils/trpc'; 11 | enableScreens(true); 12 | const { manifest } = Constants; 13 | 14 | const localhost = `http://${manifest.debuggerHost?.split(':').shift()}:3000`; 15 | 16 | export default function App() { 17 | const [queryClient] = useState(() => new QueryClient()); 18 | const [trpcClient] = useState(() => 19 | trpc.createClient({ 20 | url: `${localhost}/api/trpc`, 21 | 22 | async headers() { 23 | return {}; 24 | }, 25 | transformer, 26 | }), 27 | ); 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /apps/expo/app.config.js: -------------------------------------------------------------------------------- 1 | const STAGE = process.env.STAGE; 2 | 3 | const envConfig = { 4 | development: { 5 | scheme: 'com.example.development', 6 | icon: './assets/icon.development.png', 7 | backgroundColor: '#FF0000', 8 | }, 9 | staging: { 10 | scheme: 'com.example.staging', 11 | icon: './assets/icon.staging.png', 12 | backgroundColor: '#8000FF', 13 | }, 14 | production: { 15 | scheme: 'com.example', 16 | icon: './assets/icon.png', 17 | backgroundColor: '#1610FF', 18 | }, 19 | }; 20 | 21 | const config = envConfig[STAGE || 'development']; 22 | 23 | export default { 24 | name: 'Example', 25 | description: 'Expo + Next.js Monorepo Example', 26 | slug: 'example', 27 | scheme: 'example', 28 | owner: 'poolpoolpool', 29 | icon: config.icon, 30 | sdkVersion: '42.0.0', 31 | version: '0.0.1', 32 | splash: { 33 | image: './assets/splash.png', 34 | resizeMode: 'contain', 35 | backgroundColor: '#000000', 36 | }, 37 | ios: { 38 | bundleIdentifier: config.scheme, 39 | supportsTablet: true, 40 | }, 41 | android: { 42 | package: config.scheme, 43 | versionCode: 1, 44 | adaptiveIcon: { 45 | foregroundImage: './assets/adaptive-icon.png', 46 | backgroundColor: config.backgroundColor, 47 | }, 48 | jsEngine: 'hermes', 49 | }, 50 | androidNavigationBar: { 51 | barStyle: 'dark-content', 52 | backgroundColor: '#FFFFFF', 53 | }, 54 | assetBundlePatterns: ['**/*'], 55 | orientation: 'portrait', 56 | updates: { 57 | fallbackToCacheTimeout: 0, 58 | }, 59 | hooks: { 60 | postPublish: [ 61 | { 62 | file: 'sentry-expo/upload-sourcemaps', 63 | config: {}, 64 | }, 65 | ], 66 | }, 67 | extra: { 68 | STAGE: process.env.STAGE, 69 | }, 70 | plugins: ['sentry-expo'], 71 | }; 72 | -------------------------------------------------------------------------------- /apps/expo/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trpc/zart/4aaed67ce41ffc3d7e4eb1e7582fa98834f074e6/apps/expo/assets/adaptive-icon.png -------------------------------------------------------------------------------- /apps/expo/assets/icon.development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trpc/zart/4aaed67ce41ffc3d7e4eb1e7582fa98834f074e6/apps/expo/assets/icon.development.png -------------------------------------------------------------------------------- /apps/expo/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trpc/zart/4aaed67ce41ffc3d7e4eb1e7582fa98834f074e6/apps/expo/assets/icon.png -------------------------------------------------------------------------------- /apps/expo/assets/icon.staging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trpc/zart/4aaed67ce41ffc3d7e4eb1e7582fa98834f074e6/apps/expo/assets/icon.staging.png -------------------------------------------------------------------------------- /apps/expo/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trpc/zart/4aaed67ce41ffc3d7e4eb1e7582fa98834f074e6/apps/expo/assets/splash.png -------------------------------------------------------------------------------- /apps/expo/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "release": { 4 | "releaseChannel": "production", 5 | "env": { 6 | "STAGE": "production" 7 | } 8 | }, 9 | "preview": { 10 | "releaseChannel": "staging", 11 | "distribution": "internal", 12 | "android": { 13 | "buildType": "apk" 14 | }, 15 | "env": { 16 | "STAGE": "staging" 17 | } 18 | }, 19 | "development": { 20 | "developmentClient": true, 21 | "distribution": "internal", 22 | "env": { 23 | "STAGE": "development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/expo/index.js: -------------------------------------------------------------------------------- 1 | import 'expo-dev-client'; 2 | import 'expo-dev-launcher'; 3 | import 'expo/build/Expo.fx'; 4 | import { activateKeepAwake } from 'expo-keep-awake'; 5 | import { registerRootComponent } from 'expo'; 6 | 7 | import App from './App'; 8 | 9 | if (__DEV__) { 10 | activateKeepAwake(); 11 | } 12 | 13 | registerRootComponent(App); 14 | -------------------------------------------------------------------------------- /apps/expo/metro.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const { createMetroConfiguration } = require('expo-yarn-workspaces'); 3 | 4 | module.exports = createMetroConfiguration(__dirname); 5 | -------------------------------------------------------------------------------- /apps/expo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js", 3 | "name": "@expo-next-monorepo-example/expo", 4 | "scripts": { 5 | "start": "react-native start", 6 | "start:expo": "expo start", 7 | "start:clear": "yarn start:expo -c", 8 | "start:dev-client": "yarn expo:prebuild && yarn start:expo --dev-client", 9 | "start:dev-client:clear": "yarn start:dev-client -c", 10 | "start:android": "expo --android", 11 | "start:ios": "expo --ios", 12 | "android": "react-native run-android", 13 | "ios": "react-native run-ios", 14 | "dev": "yarn start:expo", 15 | "eject": "expo eject", 16 | "expo:prebuild": "expo prebuild --skip-dependency-update react", 17 | "run:ios": "yarn expo:prebuild && expo run:ios", 18 | "run:android": "yarn expo:prebuild && expo run:android", 19 | "postinstall": "expo-yarn-workspaces postinstall && cd ../..", 20 | "publish:development": "STAGE=development expo publish", 21 | "publish:staging": "STAGE=staging expo publish --release-channel=staging", 22 | "publish:production": "STAGE=production expo publish --release-channel=production", 23 | "build:development": "STAGE=development eas build --profile development --platform all", 24 | "build:development:ios": "STAGE=development eas build --profile development --platform ios", 25 | "build:development:android": "STAGE=development eas build --profile development --platform android", 26 | "build:preview": "STAGE=staging eas build --profile preview --platform all", 27 | "build:preview:ios": "STAGE=staging eas build --profile preview --platform ios", 28 | "build:preview:android": "STAGE=staging eas build --profile preview --platform android", 29 | "build:production": "STAGE=production eas build --profile production --platform all", 30 | "build:production:ios": "STAGE=production eas build --profile production --platform ios", 31 | "build:production:android": "STAGE=production eas build --profile production --platform android" 32 | }, 33 | "dependencies": { 34 | "@gorhom/bottom-sheet": "^4.0.0-alpha.30", 35 | "@sentry/react-native": "^2.6.2", 36 | "@trpc/client": "^9.6.0", 37 | "@trpc/react": "^9.6.0", 38 | "app": "*", 39 | "dripsy": "^2.3.6", 40 | "expo": "^42.0.0-beta.1", 41 | "expo-app-loading": "^1.0.3", 42 | "expo-application": "~3.2.0", 43 | "expo-constants": "~11.0.1", 44 | "expo-dev-client": "^0.4.7", 45 | "expo-device": "^3.3.0", 46 | "expo-font": "~9.2.1", 47 | "expo-haptics": "~10.1.0", 48 | "expo-linking": "~2.3.1", 49 | "expo-modules-core": "^0.2.0", 50 | "expo-notifications": "^0.12.3", 51 | "expo-splash-screen": "~0.11.1", 52 | "expo-status-bar": "~1.0.4", 53 | "expo-updates": "~0.8.4", 54 | "hermes-engine": "0.5.2-rc1", 55 | "react": "^17.0.2", 56 | "react-dom": "^17.0.2", 57 | "react-native": "~0.63.4", 58 | "react-native-gesture-handler": "~1.10.2", 59 | "react-native-reanimated": "2.2.0", 60 | "react-native-safe-area-context": "3.2.0", 61 | "react-native-screens": "~3.6.0", 62 | "react-native-unimodules": "~0.14.5", 63 | "react-native-web": "^0.17.1", 64 | "react-query": "^3.23.2", 65 | "sentry-expo": "^4.0.0", 66 | "superjson": "^1.7.5", 67 | "unimodules-app-loader": "~2.2.0", 68 | "unimodules-constants-interface": "^6.1.0", 69 | "unimodules-file-system-interface": "^6.1.0", 70 | "unimodules-font-interface": "^6.1.0", 71 | "unimodules-permissions-interface": "^6.1.0" 72 | }, 73 | "devDependencies": { 74 | "@babel/core": "^7.14.8", 75 | "@expo/next-adapter": "^3.0.5", 76 | "@types/react": "^17.0.20", 77 | "@types/react-native": "~0.63.2", 78 | "expo-cli": "^4.11.0", 79 | "expo-yarn-workspaces": "^1.7.0", 80 | "next-compose-plugins": "^2.2.1", 81 | "typescript": "4.4.2" 82 | }, 83 | "private": true, 84 | "version": "1.0.0", 85 | "expo-yarn-workspaces": { 86 | "symlinks": [ 87 | "react-native", 88 | "react-native-unimodules", 89 | "expo-modules-core", 90 | "react-native-reanimated", 91 | "react-native-safe-area-context", 92 | "sentry-expo", 93 | "@sentry/cli", 94 | "@sentry/react-native", 95 | "@expo/next-adapter", 96 | "expo-app-loading", 97 | "expo-application", 98 | "expo-constants", 99 | "expo-device", 100 | "expo-error-recovery", 101 | "expo-file-system", 102 | "expo-font", 103 | "expo-haptics", 104 | "expo-keep-awake", 105 | "expo-linking", 106 | "expo-permissions", 107 | "expo-splash-screen", 108 | "expo-status-bar", 109 | "expo-structured-headers", 110 | "expo-updates", 111 | "expo-dev-client", 112 | "expo-dev-launcher", 113 | "expo-dev-menu-interface", 114 | "expo-dev-menu", 115 | "expo-updates-interface", 116 | "expo-notifications", 117 | "unimodules-app-loader", 118 | "unimodules-barcode-scanner-interface", 119 | "unimodules-camera-interface", 120 | "unimodules-constants-interface", 121 | "@unimodules/core", 122 | "unimodules-face-detector-interface", 123 | "unimodules-file-system-interface", 124 | "unimodules-font-interface", 125 | "unimodules-image-loader-interface", 126 | "unimodules-permissions-interface", 127 | "@unimodules/react-native-adapter", 128 | "unimodules-sensors-interface", 129 | "unimodules-task-manager-interface" 130 | ] 131 | }, 132 | "react-native-unimodules": { 133 | "android": { 134 | "modulesPaths": [ 135 | "../../../../", 136 | "../../../../packages" 137 | ], 138 | "configuration": "api", 139 | "target": "react-native" 140 | }, 141 | "ios": { 142 | "modules_paths": [ 143 | "../../../", 144 | "../../../packages" 145 | ], 146 | "flags": { 147 | "inhibit_warnings": false 148 | } 149 | } 150 | }, 151 | "license": "MIT" 152 | } 153 | -------------------------------------------------------------------------------- /apps/expo/react-native.config.js: -------------------------------------------------------------------------------- 1 | // File created by expo-dev-client/app.plugin.js 2 | 3 | module.exports = { 4 | dependencies: { 5 | ...require('expo-dev-client/dependencies'), 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/expo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "preserve", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true, 10 | "strict": false, 11 | "target": "esnext", 12 | "allowJs": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "isolatedModules": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"], 20 | "extends": "expo/tsconfig.base" 21 | } 22 | -------------------------------------------------------------------------------- /apps/expo/utils/trpc.tsx: -------------------------------------------------------------------------------- 1 | export * from '@zart/react/trpc'; 2 | -------------------------------------------------------------------------------- /apps/nextjs/.env: -------------------------------------------------------------------------------- 1 | # Environment variables declared in this file are automatically made available to Prisma. 2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#using-environment-variables 3 | 4 | # Prisma supports the native connection string format for PostgreSQL, MySQL and SQLite. 5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings 6 | 7 | DATABASE_URL="postgresql://postgres:@localhost:5466/zart" 8 | -------------------------------------------------------------------------------- /apps/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | *.db 37 | *.db-journal 38 | prisma/_sqlite/migrations 39 | -------------------------------------------------------------------------------- /apps/nextjs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "prisma.prisma" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /apps/nextjs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /apps/nextjs/README.md: -------------------------------------------------------------------------------- 1 | # Prisma + tRPC 2 | 3 | Try in CodeSandbox: [https://githubbox.com/trpc/trpc/tree/main/examples/next-prisma-starter](https://codesandbox.io/s/github/trpc/trpc/tree/main/examples/next-prisma-starter?file=/src/pages/index.tsx) 4 | 5 | 6 | ## Features 7 | 8 | - 🧙‍♂️ E2E typesafety with [tRPC](https://trpc.io) 9 | - ⚡ Full-stack React with Next.js 10 | - ⚡ Database with Prisma 11 | - ⚙️ VSCode extensions 12 | - 🎨 ESLint + Prettier 13 | - 💚 CI setup using GitHub Actions: 14 | - ✅ E2E testing with [Playwright](https://playwright.dev/) 15 | - ✅ Linting 16 | 17 | 18 | ## Setup 19 | 20 | ```bash 21 | npx create-next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter 22 | cd trpc-prisma-starter 23 | yarn 24 | yarn dev 25 | ``` 26 | 27 | ## Files of note 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
PathDescription
./prisma/schema.prismaPrisma schema
./src/api/trpc/[trpc].tsxtRPC response handler
./src/routersYour app's different tRPC-routers
51 | 52 | ## Commands 53 | 54 | ```bash 55 | yarn dx # runs prisma studio + next 56 | yarn build # runs `prisma generate` + `prisma migrate` + `next build` 57 | yarn test-dev # runs e2e tests on dev 58 | yarn test-start # runs e2e tests on `next start` - build required before 59 | yarn dev-nuke # resets local db 60 | ``` 61 | 62 | ## ℹ️ How to switch from SQLite to Postgres 63 | 64 | How to switch to postgres 65 | 66 | - Remove migrations: `rm -rf ./prisma/migrations` 67 | - Update: `./prisma/schema.prisma` (see commented code) 68 | 69 | --- 70 | 71 | Created by [@alexdotjs](https://twitter.com/alexdotjs). 72 | -------------------------------------------------------------------------------- /apps/nextjs/jest-playwright.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/playwright-community/jest-playwright/#configuration 2 | module.exports = { 3 | browsers: ['chromium', 'firefox', 'webkit'], 4 | exitOnPageError: false, // GitHub currently throws errors 5 | launchOptions: { 6 | headless: true, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /apps/nextjs/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | preset: 'jest-playwright-preset', 4 | transform: { 5 | '^.+\\.ts$': 'ts-jest', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/nextjs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /apps/nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @link https://nextjs.org/docs/api-reference/next.config.js/introduction 3 | */ 4 | module.exports = { 5 | experimental: { 6 | externalDir: true, 7 | eslint: { 8 | // This allows production builds to successfully complete even if the project has ESLint errors. 9 | ignoreDuringBuilds: true, 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /apps/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@zart/nextjs", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "predev": "prisma generate", 7 | "dev": "next dev", 8 | "migrate-dev": "prisma migrate dev", 9 | "migrate": "prisma migrate deploy", 10 | "start": "next start", 11 | "build:1-generate": "prisma generate", 12 | "build:2-migrate": "prisma migrate deploy", 13 | "build:3-build": "next build", 14 | "build": "run-s build:* --print-label", 15 | "studio": "prisma studio", 16 | "test-dev": "start-server-and-test dev 3000 test", 17 | "test-start": "start-server-and-test start 3000 test", 18 | "test": "jest" 19 | }, 20 | "dependencies": { 21 | "@prisma/client": "^3.8.1", 22 | "@trpc/client": "^9.6.0", 23 | "@trpc/next": "^9.2.0", 24 | "@trpc/react": "^9.6.0", 25 | "@trpc/server": "^9.6.0", 26 | "clsx": "^1.1.1", 27 | "next": "^12.0.1", 28 | "prisma": "^3.8.1", 29 | "react": "^17.0.2", 30 | "react-dom": "^17.0.2", 31 | "react-query": "^3.23.2", 32 | "start-server-and-test": "^1.12.0", 33 | "superjson": "^1.7.5", 34 | "zod": "^3.0.0" 35 | }, 36 | "devDependencies": { 37 | "@types/jest": "^27.0.1", 38 | "@types/node": "^16.0.0", 39 | "@types/react": "^17.0.20", 40 | "jest": "^27.1.0", 41 | "jest-playwright": "^0.0.1", 42 | "jest-playwright-preset": "^1.4.5", 43 | "npm-run-all": "^4.1.5", 44 | "playwright": "^1.14.1", 45 | "prettier": "^2.4.0", 46 | "ts-jest": "^27.0.5", 47 | "typescript": "4.4.2" 48 | }, 49 | "publishConfig": { 50 | "access": "restricted" 51 | }, 52 | "resolutions": { 53 | "**/react": "17.0.2", 54 | "**/react-dom": "17.0.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /apps/nextjs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trpc/zart/4aaed67ce41ffc3d7e4eb1e7582fa98834f074e6/apps/nextjs/public/favicon.ico -------------------------------------------------------------------------------- /apps/nextjs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /apps/nextjs/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { httpBatchLink } from '@trpc/client/links/httpBatchLink'; 2 | import { loggerLink } from '@trpc/client/links/loggerLink'; 3 | import { withTRPC } from '@trpc/next'; 4 | import { AppType } from 'next/dist/shared/lib/utils'; 5 | import type { AppRouter } from '@zart/api/src/routers/_app'; 6 | import { transformer } from 'utils/trpc'; 7 | 8 | const MyApp: AppType = ({ Component, pageProps }) => { 9 | return ( 10 | <> 11 | 12 | 13 | ); 14 | }; 15 | 16 | function getBaseUrl() { 17 | if (process.browser) { 18 | return ''; 19 | } 20 | // // reference for vercel.com 21 | if (process.env.VERCEL_URL) { 22 | return `https://${process.env.VERCEL_URL}`; 23 | } 24 | 25 | // // reference for render.com 26 | // if (process.env.RENDER_INTERNAL_HOSTNAME) { 27 | // return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`; 28 | // } 29 | 30 | // assume localhost 31 | return `http://localhost:${process.env.PORT ?? 3000}`; 32 | } 33 | 34 | export default withTRPC({ 35 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 36 | config() { 37 | const url = `${getBaseUrl()}/api/trpc`; 38 | /** 39 | * If you want to use SSR, you need to use the server's full URL 40 | * @link https://trpc.io/docs/ssr 41 | */ 42 | return { 43 | /** 44 | * @link https://trpc.io/docs/links 45 | */ 46 | links: [ 47 | // adds pretty logs to your console in development and logs errors in production 48 | loggerLink({ 49 | enabled: (opts) => 50 | process.env.NODE_ENV === 'development' || 51 | (opts.direction === 'down' && opts.result instanceof Error), 52 | }), 53 | httpBatchLink({ 54 | url, 55 | }), 56 | ], 57 | /** 58 | * @link https://trpc.io/docs/data-transformers 59 | */ 60 | transformer, 61 | /** 62 | * @link https://react-query.tanstack.com/reference/QueryClient 63 | */ 64 | // queryClientConfig: { defaultOptions: { queries: { staleTime: 6000 } } }, 65 | }; 66 | }, 67 | /** 68 | * @link https://trpc.io/docs/ssr 69 | */ 70 | ssr: true, 71 | /** 72 | * Set headers or status code when doing SSR 73 | */ 74 | responseMeta({ clientErrors }) { 75 | if (clientErrors.length) { 76 | // propagate http first error from API calls 77 | return { 78 | status: clientErrors[0].data?.httpStatus ?? 500, 79 | }; 80 | } 81 | 82 | // for app caching with SSR see https://trpc.io/docs/caching 83 | 84 | return {}; 85 | }, 86 | })(MyApp); 87 | -------------------------------------------------------------------------------- /apps/nextjs/src/pages/api/trpc/[trpc].ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains tRPC's HTTP response handler 3 | */ 4 | import * as trpcNext from '@trpc/server/adapters/next'; 5 | import { appRouter } from '@zart/api/src/routers/_app'; 6 | import { createContext } from '@zart/api/src/createContext'; 7 | 8 | export default trpcNext.createNextApiHandler({ 9 | router: appRouter, 10 | /** 11 | * @link https://trpc.io/docs/context 12 | */ 13 | createContext, 14 | /** 15 | * @link https://trpc.io/docs/error-handling 16 | */ 17 | onError({ error }) { 18 | if (error.code === 'INTERNAL_SERVER_ERROR') { 19 | // send to bug reporting 20 | console.error('Something went wrong', error); 21 | } 22 | }, 23 | /** 24 | * Enable query batching 25 | */ 26 | batching: { 27 | enabled: true, 28 | }, 29 | /** 30 | * @link https://trpc.io/docs/caching#api-response-caching 31 | */ 32 | // responseMeta() { 33 | // // ... 34 | // }, 35 | }); 36 | -------------------------------------------------------------------------------- /apps/nextjs/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Link from 'next/link'; 3 | import { ReactQueryDevtools } from 'react-query/devtools'; 4 | import { trpc } from '../utils/trpc'; 5 | 6 | export default function IndexPage() { 7 | const utils = trpc.useContext(); 8 | const postsQuery = trpc.useQuery(['post.all']); 9 | const addPost = trpc.useMutation('post.add', { 10 | onSettled() { 11 | return utils.invalidateQuery(['post.all']); 12 | }, 13 | }); 14 | 15 | // prefetch all posts for instant navigation 16 | // useEffect(() => { 17 | // postsQuery.data?.forEach((post) => { 18 | // utils.prefetchQuery(['post.byId', post.id]); 19 | // }); 20 | // }, [postsQuery.data, utils]); 21 | 22 | return ( 23 | <> 24 | 25 | Prisma Starter 26 | 27 | 28 |

Welcome to your tRPC starter!

29 |

30 | Check the docs whenever you get 31 | stuck, or ping @alexdotjs on 32 | Twitter. 33 |

34 |

35 | Posts 36 | {postsQuery.status === 'loading' && '(loading)'} 37 |

38 | {postsQuery.data?.map((item) => ( 39 |
40 |

{item.title}

41 | 42 | View more 43 | 44 |
45 | ))} 46 | 47 |
{ 49 | e.preventDefault(); 50 | /** 51 | * In a real app you probably don't want to use this manually 52 | * Checkout React Hook Form - it works great with tRPC 53 | * @link https://react-hook-form.com/ 54 | */ 55 | 56 | const $text: HTMLInputElement = (e as any).target.elements.text; 57 | const $title: HTMLInputElement = (e as any).target.elements.title; 58 | const input = { 59 | title: $title.value, 60 | text: $text.value, 61 | }; 62 | try { 63 | await addPost.mutateAsync(input); 64 | 65 | $title.value = ''; 66 | $text.value = ''; 67 | } catch {} 68 | }} 69 | > 70 | 71 |
72 | 78 | 79 |
80 | 81 |
82 |