= {
5 | theme: {
6 | extend: {
7 | colors: {
8 | primary: '#1B416F',
9 | },
10 | },
11 | },
12 | plugins: [forms({ strategy: 'class' })],
13 | };
14 | export default config;
15 |
--------------------------------------------------------------------------------
/packages/tailwind-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/tailwind-config",
3 | "version": "1.0.0",
4 | "private": true,
5 | "exports": {
6 | ".": "./tailwind.config.ts"
7 | },
8 | "devDependencies": {
9 | "@repo/typescript-config": "workspace:*",
10 | "tailwindcss": "^3.4.0"
11 | },
12 | "dependencies": {
13 | "@tailwindcss/forms": "^0.5.7"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": ["**/.env.*local"],
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": ["dist/**"]
8 | },
9 | "lint": {
10 | "dependsOn": ["^lint"]
11 | },
12 | "dev": {
13 | "cache": false,
14 | "persistent": true
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/apps/web/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 | import ReactDOM from 'react-dom/client';
3 |
4 | import { App } from './App';
5 |
6 | const rootElement = document.getElementById('root')!;
7 |
8 | if (!rootElement.innerHTML) {
9 | const root = ReactDOM.createRoot(rootElement);
10 | root.render(
11 |
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/apps/web/src/routes/index.lazy.tsx:
--------------------------------------------------------------------------------
1 | import { createLazyFileRoute } from '@tanstack/react-router';
2 |
3 | import { trpc } from '@/utils/trpc';
4 |
5 | export const Route = createLazyFileRoute('/')({
6 | component: Index,
7 | });
8 |
9 | function Index() {
10 | const query = trpc.hello.get.useQuery({ name: 'Jonas' });
11 |
12 | return Message: {query.data?.message}
;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/typescript-config/node.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./base.json",
4 | "compilerOptions": {
5 | "lib": ["esnext", "dom"],
6 | "target": "esnext",
7 | "esModuleInterop": true,
8 | "strict": true,
9 | "moduleResolution": "node",
10 | "noEmit": true,
11 | "skipLibCheck": true,
12 |
13 | "outDir": "dist"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/apps/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/node.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 |
6 | "baseUrl": ".",
7 | "paths": {
8 | "@api/*": ["./src/*"]
9 | },
10 | "rootDir": "./src"
11 | },
12 | "tsc-alias": {
13 | "resolveFullPaths": true,
14 | "verbose": false
15 | },
16 | "include": ["src"],
17 | "exclude": ["node_modules"]
18 | }
--------------------------------------------------------------------------------
/apps/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Monorepo Starter
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/web/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react-swc';
3 | import { TanStackRouterVite } from '@tanstack/router-vite-plugin';
4 | import tailwindcss from 'tailwindcss';
5 | import tsconfigPaths from 'vite-tsconfig-paths';
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | plugins: [react(), TanStackRouterVite(), tsconfigPaths()],
10 | css: {
11 | postcss: {
12 | plugins: [tailwindcss()],
13 | },
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/apps/web/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { RouterProvider, createRouter } from '@tanstack/react-router';
2 |
3 | import { TrpcWrapper } from './components/TrpcWrapper';
4 | import './index.css';
5 | import { routeTree } from './routeTree.gen';
6 |
7 | const router = createRouter({ routeTree });
8 |
9 | declare module '@tanstack/react-router' {
10 | interface Register {
11 | router: typeof router;
12 | }
13 | }
14 |
15 | export function App() {
16 | return (
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/.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 | # Local env files
9 | .env
10 | .env.local
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 |
15 | # Testing
16 | coverage
17 |
18 | # Turbo
19 | .turbo
20 |
21 | # Vercel
22 | .vercel
23 |
24 | # Build Outputs
25 | .next/
26 | out/
27 | build
28 | dist
29 |
30 |
31 | # Debug
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 |
36 | # Misc
37 | .DS_Store
38 | *.pem
39 | .eslintcache
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/eslint-config",
3 | "version": "1.0.0",
4 | "private": true,
5 | "devDependencies": {
6 | "@typescript-eslint/eslint-plugin": "^6.17.0",
7 | "@typescript-eslint/parser": "^6.17.0",
8 | "eslint-config-prettier": "^9.1.0",
9 | "eslint-config-turbo": "^1.11.3",
10 | "eslint-plugin-import": "^2.29.1",
11 | "eslint-plugin-jsx-a11y": "^6.8.0",
12 | "eslint-plugin-only-warn": "^1.1.0",
13 | "eslint-plugin-react": "^7.34.1",
14 | "eslint-plugin-unicorn": "^52.0.0",
15 | "typescript": "^5.3.3"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/typescript-config/react.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "React Library",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "module": "EsNext",
7 | "jsx": "react-jsx",
8 |
9 |
10 | "declaration": true,
11 | "declarationMap": true,
12 | "esModuleInterop": true,
13 | "incremental": false,
14 | "isolatedModules": true,
15 | "moduleDetection": "force",
16 | "resolveJsonModule": true,
17 | "skipLibCheck": true,
18 |
19 | "strict": true,
20 | "noImplicitAny": true,
21 | "noUnusedParameters": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/api/src/trpc.ts:
--------------------------------------------------------------------------------
1 | import { initTRPC, type inferRouterInputs, type inferRouterOutputs } from '@trpc/server';
2 | import type { Request, Response } from 'express';
3 | import superjson from 'superjson';
4 |
5 | import type { AppRouter } from '@api/router';
6 |
7 | type Context = {
8 | req: Request;
9 | res: Response;
10 | };
11 |
12 | const trpc = initTRPC.context().create({
13 | transformer: superjson,
14 | });
15 |
16 | export const publicProcedure = trpc.procedure;
17 | export const router = trpc.router;
18 |
19 | export type RouterInput = inferRouterInputs;
20 | export type RouterOutput = inferRouterOutputs;
21 |
--------------------------------------------------------------------------------
/packages/eslint-config/react.js:
--------------------------------------------------------------------------------
1 | /** @type {import("eslint").Linter.Config} */
2 | module.exports = {
3 | extends: ['./eslint', 'plugin:react/recommended', 'plugin:jsx-a11y/recommended'],
4 | plugins: ['jsx-a11y'],
5 | globals: {
6 | React: true,
7 | JSX: true,
8 | },
9 | rules: {
10 | 'react/react-in-jsx-scope': 'off',
11 | 'prefer-template': 'off',
12 | 'react/prop-types': 'off',
13 | 'jsx-a11y/anchor-is-valid': 'off',
14 | },
15 | env: {
16 | browser: true,
17 | },
18 | settings: { react: { version: '18.2.0' } },
19 | overrides: [
20 | // Force ESLint to detect .tsx files
21 | { files: ['*.js?(x)', '*.ts?(x)'] },
22 | ],
23 | };
24 |
--------------------------------------------------------------------------------
/apps/api/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://docs.npmjs.com/cli/shrinkwrap#caveats
27 | node_modules
28 |
29 | # Debug log from npm
30 | npm-debug.log
31 |
32 | .DS_Store
33 |
34 | .env
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-trpc-turbo",
3 | "version": "1.0.0",
4 | "private": true,
5 | "packageManager": "pnpm@8.9.0",
6 | "engines": {
7 | "node": ">=18"
8 | },
9 | "scripts": {
10 | "build": "turbo build",
11 | "dev": "turbo dev",
12 | "lint": "turbo lint",
13 | "format": "prettier --write \"**/*.{ts,tsx,md}\"",
14 | "start:backend": "node apps/api/dist/server.js"
15 | },
16 | "devDependencies": {
17 | "@repo/eslint-config": "workspace:*",
18 | "@repo/tailwind-config": "workspace:*",
19 | "@repo/typescript-config": "workspace:*",
20 | "prettier": "^3.3.3",
21 | "prettier-plugin-tailwindcss": "^0.6.5",
22 | "turbo": "latest"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/web/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@repo/typescript-config/react.json",
3 | "compilerOptions": {
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | // Custom settings
18 | "baseUrl": ".",
19 | "paths": {
20 | "@/*": ["./src/*"],
21 | "@api/*": ["../api/src/*"]
22 | }
23 | },
24 | "include": ["src"],
25 | "references": [{ "path": "./tsconfig.node.json" }]
26 | }
27 |
--------------------------------------------------------------------------------
/apps/web/src/components/TrpcWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2 | import { httpBatchLink } from '@trpc/client';
3 | import { useState } from 'react';
4 | import superjson from 'superjson';
5 |
6 | import { trpc } from '@/utils/trpc';
7 |
8 | export function TrpcWrapper({ children }: { children: React.ReactNode }) {
9 | const [queryClient] = useState(() => new QueryClient());
10 | const [trpcClient] = useState(() =>
11 | trpc.createClient({
12 | links: [
13 | httpBatchLink({
14 | url: import.meta.env.VITE_API_URL + '/trpc',
15 | transformer: superjson,
16 | }),
17 | ],
18 | })
19 | );
20 |
21 | return (
22 |
23 | {children}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/apps/api/src/server.ts:
--------------------------------------------------------------------------------
1 | import { createExpressMiddleware } from '@trpc/server/adapters/express';
2 | import cors from 'cors';
3 | import 'dotenv/config';
4 | import express from 'express';
5 |
6 | import { appRouter } from '@api/router';
7 |
8 |
9 | async function main() {
10 | const port = process.env.PORT || 3000;
11 |
12 | const app = express();
13 |
14 | app.use(cors());
15 |
16 |
17 | app.use(
18 | '/trpc',
19 | createExpressMiddleware({
20 | router: appRouter,
21 | createContext: (ctx) => ({ req: ctx.req, res: ctx.res }),
22 | onError:
23 | process.env.NODE_ENV === 'development'
24 | ? ({ path, error }) => {
25 | console.error(`❌ tRPC failed on ${path ?? ''}: ${error.message}`);
26 | }
27 | : undefined,
28 | })
29 | );
30 |
31 |
32 | // For testing purposes, wait-on requests '/'
33 | app.get('/', (req, res) => res.send('Server is running!'));
34 |
35 | app.listen(port, () => {
36 | console.log(`App listening on port: ${port}`);
37 | });
38 | }
39 |
40 | void main();
41 |
--------------------------------------------------------------------------------
/apps/web/src/routeTree.gen.ts:
--------------------------------------------------------------------------------
1 | /* prettier-ignore-start */
2 |
3 | /* eslint-disable */
4 |
5 | // @ts-nocheck
6 |
7 | // noinspection JSUnusedGlobalSymbols
8 |
9 | // This file is auto-generated by TanStack Router
10 |
11 | import { createFileRoute } from '@tanstack/react-router'
12 |
13 | // Import Routes
14 |
15 | import { Route as rootRoute } from './routes/__root'
16 |
17 | // Create Virtual Routes
18 |
19 | const IndexLazyImport = createFileRoute('/')()
20 |
21 | // Create/Update Routes
22 |
23 | const IndexLazyRoute = IndexLazyImport.update({
24 | path: '/',
25 | getParentRoute: () => rootRoute,
26 | } as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route))
27 |
28 | // Populate the FileRoutesByPath interface
29 |
30 | declare module '@tanstack/react-router' {
31 | interface FileRoutesByPath {
32 | '/': {
33 | preLoaderRoute: typeof IndexLazyImport
34 | parentRoute: typeof rootRoute
35 | }
36 | }
37 | }
38 |
39 | // Create and export the route tree
40 |
41 | export const routeTree = rootRoute.addChildren([IndexLazyRoute])
42 |
43 | /* prettier-ignore-end */
44 |
--------------------------------------------------------------------------------
/apps/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/api",
3 | "private": true,
4 | "type": "module",
5 | "exports": {
6 | ".": "./src/index.ts"
7 | },
8 | "scripts": {
9 | "dev": "NODE_ENV=development tsx watch src/server",
10 | "lint": "eslint --cache --ext \".js,.ts,.tsx\" src",
11 | "type-check": "tsc",
12 | "build": "rimraf dist && tsc --noEmit false && tsc-alias",
13 | "start": "node dist/server.js"
14 | },
15 | "dependencies": {
16 | "@trpc/server": "11.0.0-next-beta.289",
17 | "cors": "^2.8.5",
18 | "dotenv": "^16.4.5",
19 | "express": "^4.17.1",
20 | "jsonwebtoken": "^9.0.2",
21 | "superjson": "^2.2.1",
22 | "zod": "^3.0.0"
23 | },
24 | "devDependencies": {
25 | "@repo/eslint-config": "workspace:*",
26 | "@repo/typescript-config": "workspace:*",
27 | "@types/cors": "^2.8.17",
28 | "@types/express": "^4.17.17",
29 | "@types/jsonwebtoken": "^9.0.6",
30 | "@types/node": "^20.10.0",
31 | "eslint": "^8.56.0",
32 | "rimraf": "^5.0.5",
33 | "tsc-alias": "^1.8.8",
34 | "tsx": "^4.0.0",
35 | "typescript": "^5.3.3"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Noah Falk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/eslint-config/eslint.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('node:path');
2 |
3 | const project = resolve(process.cwd(), 'tsconfig.json');
4 |
5 | /** @type {import("eslint").Linter.Config} */
6 | module.exports = {
7 | parser: '@typescript-eslint/parser',
8 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
9 | plugins: ['@typescript-eslint', 'import', 'unicorn'],
10 | rules: {
11 | '@typescript-eslint/no-unused-vars': 'error',
12 | '@typescript-eslint/no-explicit-any': 'error',
13 | 'import/first': 'error',
14 | 'import/newline-after-import': 'error',
15 | 'arrow-body-style': ['error', 'as-needed'],
16 | 'no-return-await': ['error'],
17 | 'object-shorthand': ['error'],
18 | 'no-unneeded-ternary': ['error'],
19 | '@typescript-eslint/no-namespace': 'off',
20 | 'prefer-template': ['error'],
21 | '@typescript-eslint/consistent-type-imports': ['error', { fixStyle: 'inline-type-imports' }],
22 | 'no-empty': ['error', { allowEmptyCatch: true }],
23 | },
24 | settings: {
25 | 'import/resolver': {
26 | typescript: {
27 | project,
28 | },
29 | },
30 | },
31 | ignorePatterns: [
32 | // Ignore dotfiles
33 | '.*.js',
34 | 'node_modules/',
35 | 'dist/',
36 | ],
37 | };
38 |
--------------------------------------------------------------------------------
/apps/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@repo/web",
3 | "version": "1.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@tanstack/react-query": "^5.20.5",
13 | "@tanstack/react-router": "^1.16.2",
14 | "@trpc/client": "11.0.0-next-beta.289",
15 | "@trpc/react-query": "11.0.0-next-beta.289",
16 | "react": "^18.2.0",
17 | "react-dom": "^18.2.0",
18 | "superjson": "^2.2.1",
19 | "zod": "^3.0.0"
20 | },
21 | "devDependencies": {
22 | "@repo/api": "workspace:*",
23 | "@repo/tailwind-config": "workspace:*",
24 | "@repo/typescript-config": "workspace:*",
25 | "@tanstack/router-vite-plugin": "^1.16.3",
26 | "@types/react": "^18.2.55",
27 | "@types/react-dom": "^18.2.19",
28 | "@vitejs/plugin-react-swc": "^3.5.0",
29 | "autoprefixer": "^10.4.17",
30 | "eslint": "^8.56.0",
31 | "eslint-plugin-react-hooks": "^4.6.0",
32 | "eslint-plugin-react-refresh": "^0.4.5",
33 | "postcss": "^8.4.35",
34 | "tailwind-merge": "^2.2.1",
35 | "tailwindcss": "^3.4.0",
36 | "typescript": "^5.2.2",
37 | "vite": "^5.1.0",
38 | "vite-tsconfig-paths": "^4.3.2"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-trpc-turbo
2 |
3 | ## Technologies used
4 |
5 | - Turborepo
6 | - React Vite
7 | - Express.js
8 | - tRPC
9 | - TanStack Router
10 | - Tailwind CSS
11 |
12 | ### Apps and Packages
13 |
14 | - `@repo/web`: Vite, React, TanStack Router and tRPC Client
15 | - `@repo/api`: Express.js, Drizzle and tRPC Server
16 | - `@repo/eslint-config`: `eslint` configurations
17 | - `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
18 | - `@repo/tailwind-config`: shared Tailwind configuration
19 |
20 | Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
21 |
22 | ### Utilities
23 |
24 | This Turborepo has some additional tools already setup for you:
25 |
26 | - [TypeScript](https://www.typescriptlang.org/) for static type checking
27 | - [ESLint](https://eslint.org/) for code linting
28 | - [Prettier](https://prettier.io) for code formatting
29 |
30 | ## Setup
31 |
32 | To get started, clone the repository and install the dependencies:
33 |
34 | ```
35 | pnpm install
36 | ```
37 |
38 | Then, copy the `.env.example` file to `.env` in the web/ folder and fill in the necessary environment variables. For local development, the defaul value will work. If you want to deploy the app, you will need to specify where the backend is hosted.
39 |
40 | ```
41 | cp ./apps/web/.env.example ./apps/web/.env
42 | ```
43 |
44 | ### Build
45 |
46 | To build all apps and packages, run the following command:
47 |
48 | ```
49 | pnpm build
50 | ```
51 |
52 | ### Develop
53 |
54 | To run all apps and packages in development mode, run the following command:
55 |
56 | ```
57 | pnpm dev
58 | ```
59 |
--------------------------------------------------------------------------------