├── .commitlintrc.ts ├── .github └── workflows │ ├── actions │ └── setup_node-cache_dep-install_dep │ │ └── action.yml │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── pre-push ├── .lintstagedrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── src ├── db │ ├── Batch.ts │ ├── User.ts │ ├── index.ts │ └── instance.ts ├── index.ts ├── trpc │ ├── index.ts │ └── router │ │ ├── batchRouter.ts │ │ ├── index.ts │ │ ├── userRouter.ts │ │ └── utilRouter.ts └── utils │ ├── homePage.ts │ ├── index.ts │ ├── mocks │ ├── mockBatches.ts │ └── mockUsers.ts │ ├── sleep │ └── index.ts │ ├── types │ ├── index.ts │ └── process-env.ts │ └── version │ ├── VersionInfo.ts │ └── index.ts ├── trpc-api-export ├── builder │ ├── index.ts │ ├── tsconfig.build.json │ └── tsup.config.ts └── dist │ ├── index.d.ts │ └── index.js └── tsconfig.json /.commitlintrc.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from '@commitlint/types'; 2 | 3 | const commitlintConfig: UserConfig = { 4 | extends: ['@commitlint/config-conventional'], 5 | }; 6 | 7 | // eslint-disable-next-line import/no-default-export 8 | export default commitlintConfig; 9 | -------------------------------------------------------------------------------- /.github/workflows/actions/setup_node-cache_dep-install_dep/action.yml: -------------------------------------------------------------------------------- 1 | name: 'setup_node-cache_dep-install_dep' 2 | description: 'Setup Node.js ⚙️ - Cache dependencies ⚡ - Install dependencies 🔧' 3 | 4 | runs: 5 | using: 'composite' 6 | steps: 7 | - name: Setup Node.js ⚙️ & Cache dependencies ⚡ 8 | uses: actions/setup-node@v4 9 | with: 10 | node-version: '22.x' 11 | cache: 'npm' 12 | 13 | - name: Install dependencies 🔧 14 | shell: bash 15 | run: npm ci 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | FILE_PATH_VERSION_INFO: ./src/utils/version/VersionInfo.ts 10 | 11 | jobs: 12 | build: 13 | name: Build 🏗️ 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 🛎️ 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup Node.js ⚙️ - Cache dependencies ⚡ - Install dependencies 🔧 21 | uses: ./.github/workflows/actions/setup_node-cache_dep-install_dep 22 | 23 | - name: Version Info 💉 24 | run: | 25 | COMMIT=$GITHUB_SHA 26 | BRANCH=${GITHUB_REF#refs/heads/} 27 | TAG='' 28 | if [[ ${{ github.ref_type }} == 'tag' ]]; then 29 | TAG=${GITHUB_REF#refs/tags/} 30 | fi 31 | echo "write tag, branch & commit id to $FILE_PATH_VERSION_INFO" 32 | echo "export const VersionInfo = { tag: '$TAG', branch: '$BRANCH', commit: '$COMMIT' };" > $FILE_PATH_VERSION_INFO 33 | cat $FILE_PATH_VERSION_INFO 34 | 35 | - name: Build 🏗️ 36 | run: npm run build 37 | 38 | lint: 39 | name: Lint ✅ 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - name: Checkout 🛎️ 44 | uses: actions/checkout@v4 45 | 46 | - name: Setup Node.js ⚙️ - Cache dependencies ⚡ - Install dependencies 🔧 47 | uses: ./.github/workflows/actions/setup_node-cache_dep-install_dep 48 | 49 | - name: Lint ✅ 50 | run: npm run lint 51 | 52 | tsc: 53 | name: TypeScript Compiler 🔎 54 | runs-on: ubuntu-latest 55 | 56 | steps: 57 | - name: Checkout 🛎️ 58 | uses: actions/checkout@v4 59 | 60 | - name: Setup Node.js ⚙️ - Cache dependencies ⚡ - Install dependencies 🔧 61 | uses: ./.github/workflows/actions/setup_node-cache_dep-install_dep 62 | 63 | - name: TypeScript Compiler 🔎 64 | run: npm run tsc 65 | 66 | release-npm: 67 | name: Release npm 📦 68 | needs: [build, lint, tsc] 69 | runs-on: ubuntu-latest 70 | 71 | steps: 72 | - name: Checkout 🛎️ 73 | uses: actions/checkout@v4 74 | 75 | - name: Setup Node.js ⚙️ - Cache dependencies ⚡ - Install dependencies 🔧 76 | uses: ./.github/workflows/actions/setup_node-cache_dep-install_dep 77 | 78 | - name: Export tRPC API 🏗️ 79 | run: npm run trpc-api-export 80 | 81 | - name: Release npm 📦 82 | env: 83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 84 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 85 | run: npm run release 86 | 87 | deploy: 88 | name: Deploy 🚀 89 | needs: [build, lint, tsc] 90 | runs-on: ubuntu-latest 91 | 92 | environment: 93 | name: tRPC API 94 | url: https://trpc-api-boilerplate.onrender.com 95 | 96 | steps: 97 | - name: Deploy 🚀 98 | uses: fjogeleit/http-request-action@v1 99 | with: 100 | url: ${{ secrets.RENDER_DEPLOY_HOOK }} 101 | method: 'GET' 102 | timeout: 20000 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | compiled 5 | 6 | .DS_Store 7 | *.log 8 | .vscode 9 | .idea 10 | 11 | npm-debug.log* 12 | 13 | *.env 14 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run lint-staged-husky -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | npm run trpc-api-export -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*": ["prettier --write --ignore-unknown"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | build -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 mkosir 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tRPC API Boilerplate ![Heisenberg](https://raw.githubusercontent.com/mkosir/trpc-fe-boilerplate-vite/main/misc/heisenberg_75.png) 2 | 3 | [![CI][ci-badge]][ci-url] 4 | 5 | Minimal [tRPC](https://trpc.io/) API boilerplate for separate backend and frontend repositories. Easily publish fully typesafe APIs that any frontend can consume. 6 | 7 | Monorepos are great, but sometimes we are targeting backend and frontend as separate (mono)repositories. 8 | 9 | We might aim for backend and frontend repositories separation of: 10 | 11 | - domain/business logic - expose only what need to be exposed through API. 12 | - developers - larger teams/companies. 13 | - CI/CD pipelines, PRs, issues, etc. 14 | 15 | ... in that case checkout this boilerplate. 16 | 17 | ## Running 18 | 19 | _Easily set up a local development environment_ 20 | 21 | - fork & clone repo 22 | - `npm install` 23 | - make changes to tRPC API & push - new [package](https://www.npmjs.com/package/trpc-api-boilerplate) is released 📦 [![npm version][npm-badge]][npm-url] 24 | - install newly released package `npm install trpc-api-boilerplate` in any frontend app 🚀 25 | 26 | ## Example Repo 27 | 28 | Example frontend app repositories: 29 | 30 | - [tRPC Frontend Boilerplate](https://github.com/mkosir/trpc-fe-boilerplate-vite) - Vite 31 | - [tRPC Frontend Boilerplate](https://github.com/mkosir/trpc-fe-boilerplate-next) - Nextjs 32 | 33 | ### Avoid publishing package? 34 | 35 | If for whatever reason publishing a package is not an option: 36 | 37 | - privacy concerns 38 | - faster development iterations - skip CI 39 | - ... 40 | 41 | Use repository to share types by running `npm run trpc-api-export` and push code changes. 42 | In your [frontend app](https://github.com/mkosir/trpc-fe-boilerplate-vite/blob/main/package.json#L7) consume types by running `npm run trpc-api-import`. 43 | 44 | 45 | 46 | [ci-badge]: https://github.com/mkosir/trpc-api-boilerplate/actions/workflows/main.yml/badge.svg 47 | [ci-url]: https://github.com/mkosir/trpc-api-boilerplate/actions/workflows/main.yml 48 | [npm-url]: https://www.npmjs.com/package/trpc-api-boilerplate 49 | [npm-badge]: https://img.shields.io/npm/v/trpc-api-boilerplate.svg 50 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import eslintConfigPrettier from 'eslint-config-prettier'; 3 | import eslintPluginImport from 'eslint-plugin-import'; 4 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | eslint.configs.recommended, 9 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access 10 | eslintPluginImport.flatConfigs.recommended, 11 | ...tseslint.configs.strictTypeChecked, 12 | ...tseslint.configs.stylisticTypeChecked, 13 | eslintPluginPrettierRecommended, 14 | eslintConfigPrettier, 15 | { 16 | ignores: ['!.*', 'node_modules', 'dist', 'trpc-api-export/dist', 'compiled', 'build'], 17 | }, 18 | { 19 | languageOptions: { 20 | parserOptions: { 21 | projectService: true, 22 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 23 | tsconfigRootDir: import.meta.name, 24 | }, 25 | }, 26 | settings: { 27 | 'import/resolver': { 28 | typescript: { project: 'tsconfig.json' }, 29 | }, 30 | }, 31 | }, 32 | { 33 | files: ['**/*.{js,ts,tsx}'], 34 | 35 | rules: { 36 | 'prefer-template': 'error', 37 | 'no-nested-ternary': 'error', 38 | 'no-unneeded-ternary': 'error', 39 | 40 | '@typescript-eslint/ban-ts-comment': ['error', { 'ts-expect-error': 'allow-with-description' }], 41 | '@typescript-eslint/consistent-type-definitions': ['error', 'type'], 42 | '@typescript-eslint/array-type': ['error', { default: 'generic' }], 43 | '@typescript-eslint/prefer-nullish-coalescing': 'error', 44 | '@typescript-eslint/no-unnecessary-condition': 'error', 45 | '@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }], 46 | '@typescript-eslint/restrict-plus-operands': 'off', 47 | '@typescript-eslint/restrict-template-expressions': 'off', 48 | '@typescript-eslint/naming-convention': [ 49 | 'error', 50 | { 51 | selector: 'typeAlias', 52 | format: ['PascalCase'], 53 | }, 54 | { 55 | selector: 'variable', 56 | types: ['boolean'], 57 | format: ['PascalCase'], 58 | prefix: ['is', 'should', 'has', 'can', 'did', 'will'], 59 | }, 60 | { 61 | // Generic type parameter must start with letter T, followed by any uppercase letter. 62 | selector: 'typeParameter', 63 | format: ['PascalCase'], 64 | custom: { regex: '^T[A-Z]', match: true }, 65 | }, 66 | ], 67 | 68 | 'import/no-default-export': 'error', 69 | 'import/order': [ 70 | 'error', 71 | { 72 | groups: ['builtin', 'external', 'internal', 'parent', 'sibling'], 73 | 'newlines-between': 'always', 74 | alphabetize: { 75 | order: 'asc', 76 | caseInsensitive: true, 77 | }, 78 | }, 79 | ], 80 | }, 81 | }, 82 | ); 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trpc-api-boilerplate", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "main": "./trpc-api-export/dist/index.js", 6 | "exports": "./trpc-api-export/dist/index.js", 7 | "types": "./trpc-api-export/dist/index.d.ts", 8 | "sideEffects": false, 9 | "files": [ 10 | "trpc-api-export/dist", 11 | "README.md" 12 | ], 13 | "scripts": { 14 | "trpc-api-export": "tsup --config trpc-api-export/builder/tsup.config.ts && npm run format-fix", 15 | "prepare": "husky", 16 | "build": "rm -rf build && tsc -p tsconfig.json", 17 | "dev": "tsx watch src/index.ts", 18 | "start": "tsx src/index.ts", 19 | "lint": "eslint --report-unused-disable-directives --max-warnings 0 .", 20 | "lint-fix": "eslint --fix .", 21 | "lint-staged-husky": "lint-staged", 22 | "tsc": "tsc -p tsconfig.json", 23 | "format-lint": "prettier --config .prettierrc --check --ignore-unknown .", 24 | "format-fix": "prettier --config .prettierrc --write --ignore-unknown -l .", 25 | "release": "semantic-release --branches main" 26 | }, 27 | "dependencies": { 28 | "@trpc/server": "11.1.2", 29 | "cors": "2.8.5", 30 | "express": "5.1.0", 31 | "superjson": "2.2.2", 32 | "zod": "3.24.3" 33 | }, 34 | "devDependencies": { 35 | "@commitlint/cli": "19.8.0", 36 | "@commitlint/config-conventional": "19.8.0", 37 | "@eslint/js": "9.26.0", 38 | "@types/cors": "2.8.17", 39 | "@types/eslint": "9.6.1", 40 | "@types/express": "5.0.1", 41 | "eslint": "9.26.0", 42 | "eslint-config-prettier": "10.1.2", 43 | "eslint-import-resolver-typescript": "4.3.4", 44 | "eslint-plugin-import": "2.31.0", 45 | "eslint-plugin-prettier": "5.3.1", 46 | "husky": "9.1.7", 47 | "lint-staged": "15.5.1", 48 | "prettier": "3.5.3", 49 | "semantic-release": "24.2.3", 50 | "tsup": "8.4.0", 51 | "tsx": "4.19.4", 52 | "typescript": "5.8.3", 53 | "typescript-eslint": "8.31.1" 54 | }, 55 | "engines": { 56 | "node": ">=22.15.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/db/Batch.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import { UserConfigSchema } from './User'; 4 | 5 | export const BatchConfigSchema = z.object({ 6 | id: z.string(), 7 | title: z.string(), 8 | description: z.string().nullable(), 9 | purity: z.number().min(0).max(100), 10 | weight: z.number().positive({ message: 'Must be a positive number.' }), 11 | producers: UserConfigSchema.array(), 12 | supplier: z.object({ id: z.string(), name: z.string(), description: z.string().nullable() }).nullable(), 13 | }); 14 | 15 | type Batch = z.infer; 16 | 17 | export type Batches = ReadonlyArray; 18 | -------------------------------------------------------------------------------- /src/db/User.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const USER_ROLES = ['administrator', 'apprentice', 'standard'] as const; 4 | 5 | export const UserConfigSchema = z.object({ 6 | id: z.string(), 7 | email: z.string(), 8 | name: z.string(), 9 | username: z.string(), 10 | imageUrl: z.string().optional(), 11 | role: z.enum(USER_ROLES), 12 | }); 13 | 14 | export type User = z.infer; 15 | 16 | export type Users = ReadonlyArray; 17 | -------------------------------------------------------------------------------- /src/db/index.ts: -------------------------------------------------------------------------------- 1 | export * from './instance'; 2 | export * from './Batch'; 3 | export * from './User'; 4 | -------------------------------------------------------------------------------- /src/db/instance.ts: -------------------------------------------------------------------------------- 1 | import { DeepMutable, mockBatches, mockUsers } from 'utils'; 2 | 3 | import { Batches } from './Batch'; 4 | import { Users } from './User'; 5 | 6 | export const db = { 7 | users: structuredClone(mockUsers) as unknown as DeepMutable, 8 | batches: structuredClone(mockBatches) as unknown as DeepMutable, 9 | }; 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as trpcExpress from '@trpc/server/adapters/express'; 2 | import cors from 'cors'; 3 | import express from 'express'; 4 | 5 | import { homePage } from 'utils'; 6 | 7 | import { createContext } from './trpc'; 8 | import { appRouter } from './trpc/router'; 9 | 10 | const app = express(); 11 | 12 | app.use(cors()); 13 | 14 | app.use( 15 | '/trpc', 16 | trpcExpress.createExpressMiddleware({ 17 | router: appRouter, 18 | createContext, 19 | }), 20 | ); 21 | 22 | // @ts-expect-error Upgrade express to v5 23 | app.use('/', (_req, res) => { 24 | return res.type('html').send(homePage); 25 | }); 26 | 27 | const PORT = process.env.PORT ?? 4000; 28 | 29 | app.listen(PORT, () => { 30 | console.log(`Server running on http://localhost:${PORT}`); 31 | }); 32 | -------------------------------------------------------------------------------- /src/trpc/index.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC } from '@trpc/server'; 2 | import * as trpcExpress from '@trpc/server/adapters/express'; 3 | import superjson from 'superjson'; 4 | 5 | export const createContext = ({ req, res }: trpcExpress.CreateExpressContextOptions) => ({ req, res }); 6 | const t = initTRPC.create({ transformer: superjson }); 7 | 8 | export const middleware = t.middleware; 9 | export const router = t.router; 10 | export const publicProcedure = t.procedure; 11 | -------------------------------------------------------------------------------- /src/trpc/router/batchRouter.ts: -------------------------------------------------------------------------------- 1 | import { db } from 'db'; 2 | import { sleep } from 'utils'; 3 | 4 | import { router, publicProcedure } from '..'; 5 | 6 | export const batchRouter = router({ 7 | list: publicProcedure.query(async () => { 8 | await sleep(1000); 9 | 10 | return db.batches; 11 | }), 12 | }); 13 | -------------------------------------------------------------------------------- /src/trpc/router/index.ts: -------------------------------------------------------------------------------- 1 | import { router } from '..'; 2 | 3 | import { batchRouter } from './batchRouter'; 4 | import { userRouter } from './userRouter'; 5 | import { utilRouter } from './utilRouter'; 6 | 7 | export const appRouter = router({ 8 | batch: batchRouter, 9 | user: userRouter, 10 | util: utilRouter, 11 | }); 12 | 13 | export type AppRouter = typeof appRouter; 14 | -------------------------------------------------------------------------------- /src/trpc/router/userRouter.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import { db, User, UserConfigSchema } from 'db'; 4 | import { sleep } from 'utils'; 5 | 6 | import { router, publicProcedure } from '../'; 7 | 8 | export const userRouter = router({ 9 | list: publicProcedure.query(async () => { 10 | await sleep(1000); 11 | 12 | return db.users; 13 | }), 14 | 15 | show: publicProcedure.input(z.string().min(1)).query(async ({ input: userId }) => { 16 | await sleep(1000); 17 | 18 | const user = db.users.find((user) => user.id === userId); 19 | 20 | if (user) { 21 | return user; 22 | } 23 | 24 | return `User with id:${userId} does not exist in database.` as const; 25 | }), 26 | 27 | destroy: publicProcedure.input(z.object({ id: z.string().min(1) })).mutation(async ({ input: { id } }) => { 28 | await sleep(1000); 29 | 30 | const index = db.users.findIndex((user) => user.id === id); 31 | const deletedUser = db.users.splice(index, 1); 32 | 33 | return deletedUser; 34 | }), 35 | 36 | create: publicProcedure 37 | .input(z.object({ user: UserConfigSchema.omit({ id: true }) })) 38 | .mutation(async ({ input: { user } }) => { 39 | await sleep(1000); 40 | 41 | const newUser: User = { id: crypto.randomUUID(), ...user }; 42 | 43 | db.users.push(newUser); 44 | 45 | return newUser; 46 | }), 47 | }); 48 | -------------------------------------------------------------------------------- /src/trpc/router/utilRouter.ts: -------------------------------------------------------------------------------- 1 | import { db, Batches, Users } from 'db'; 2 | import { DeepMutable, mockBatches, mockUsers } from 'utils'; 3 | 4 | import { router, publicProcedure } from '..'; 5 | 6 | export const utilRouter = router({ 7 | seedDb: publicProcedure.mutation(() => { 8 | db.users = structuredClone(mockUsers) as unknown as DeepMutable; 9 | db.batches = structuredClone(mockBatches) as unknown as DeepMutable; 10 | 11 | return 'Database initialized successfully.' as const; 12 | }), 13 | }); 14 | -------------------------------------------------------------------------------- /src/utils/homePage.ts: -------------------------------------------------------------------------------- 1 | import { getVersionInfo } from './version'; 2 | 3 | export const homePage = ` 4 | 5 | 6 | 7 | tRPC API Boilerplate 8 | 9 | 10 | 11 |

tRPC API Boilerplate

12 |
${getVersionInfo()}
13 |
14 |
15 | Router: 16 |
    17 | User 18 |
  • List
  • 19 |
20 |
    21 | Batch 22 |
  • List
  • 23 |
24 |
    25 | Util 26 |
  • 27 | 28 | 29 |
  • 30 |
31 |
32 | 47 | 48 | 49 | `; 50 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mocks/mockBatches'; 2 | export * from './mocks/mockUsers'; 3 | export * from './sleep'; 4 | export * from './types'; 5 | export * from './version'; 6 | export * from './homePage'; 7 | -------------------------------------------------------------------------------- /src/utils/mocks/mockBatches.ts: -------------------------------------------------------------------------------- 1 | import { Batches } from 'db'; 2 | 3 | import { mockUsers } from './mockUsers'; 4 | 5 | export const mockBatches = [ 6 | { 7 | id: '2f008731-4645-43de-8af9-3060d4086001', 8 | title: 'Blue Sky', 9 | description: 'summer batch', 10 | purity: 99.11, 11 | weight: 145.64, 12 | producers: [mockUsers[0], mockUsers[1]], 13 | supplier: { 14 | id: '3f008731-4645-43de-8af9-3060d4086001', 15 | name: 'Golden Moth Chemical', 16 | description: 'Golden bee company logo.', 17 | }, 18 | }, 19 | { 20 | id: '2f008731-4645-43de-8af9-3060d4086002', 21 | title: 'Blue Sky', 22 | description: 'bad batch', 23 | purity: 45.72, 24 | weight: 142.18, 25 | producers: [mockUsers[0], mockUsers[1]], 26 | supplier: { 27 | id: '3f008731-4645-43de-8af9-3060d4086002', 28 | name: 'Warehouse', 29 | description: 30 | "Lock security guard into portable toilet, don't roll but carry stolen barrel of methylamine, improvise as it goes...", 31 | }, 32 | }, 33 | { 34 | id: '2f008731-4645-43de-8af9-3060d4086003', 35 | title: 'Blue Sky', 36 | description: null, 37 | purity: 99.4, 38 | weight: 149.7, 39 | producers: [mockUsers[0]], 40 | supplier: null, 41 | }, 42 | { 43 | id: '2f008731-4645-43de-8af9-3060d4086004', 44 | title: 'Blue Sky', 45 | description: null, 46 | purity: 98.64, 47 | weight: 146.51, 48 | producers: [mockUsers[1]], 49 | supplier: null, 50 | }, 51 | ] satisfies Batches; 52 | -------------------------------------------------------------------------------- /src/utils/mocks/mockUsers.ts: -------------------------------------------------------------------------------- 1 | import { Users } from 'db'; 2 | 3 | const baseImageUrl = 'https://raw.githubusercontent.com/mkosir/trpc-fe-boilerplate-vite/main/misc/user-images'; 4 | 5 | export const mockUsers = [ 6 | { 7 | id: '1f008731-4645-43de-8af9-3060d4086001', 8 | email: 'walter.white@mail.com', 9 | name: 'Walter White', 10 | username: 'Heisenberg', 11 | imageUrl: `${baseImageUrl}/heisenberg.jpg`, 12 | role: 'administrator', 13 | }, 14 | { 15 | id: '1f008731-4645-43de-8af9-3060d4086002', 16 | email: 'jesse.pinkman@mail.com', 17 | name: 'Jesse Pinkman', 18 | username: 'Jesse', 19 | imageUrl: `${baseImageUrl}/jesse.jpg`, 20 | role: 'apprentice', 21 | }, 22 | { 23 | id: '1f008731-4645-43de-8af9-3060d4086003', 24 | email: 'skyler.white@mail.com', 25 | name: 'Skyler White', 26 | username: 'Sky', 27 | imageUrl: `${baseImageUrl}/sky.jpg`, 28 | role: 'standard', 29 | }, 30 | { 31 | id: '1f008731-4645-43de-8af9-3060d4086004', 32 | email: 'hank.schrader@mail.com', 33 | name: 'Hank Schrader', 34 | username: 'Hank', 35 | imageUrl: `${baseImageUrl}/hank.jpg`, 36 | role: 'standard', 37 | }, 38 | { 39 | id: '1f008731-4645-43de-8af9-3060d4086005', 40 | email: 'marie.schrader@mail.com', 41 | name: 'Marie Schrader', 42 | username: 'Marie', 43 | role: 'standard', 44 | }, 45 | { 46 | id: '1f008731-4645-43de-8af9-3060d4086006', 47 | email: 'saul.goodman@mail.com', 48 | name: 'Saul Goodman', 49 | username: 'Jimmy', 50 | imageUrl: `${baseImageUrl}/jimmy.jpg`, 51 | role: 'standard', 52 | }, 53 | { 54 | id: '1f008731-4645-43de-8af9-3060d4086007', 55 | email: 'gustavo.fring@mail.com', 56 | name: 'Gustavo Fring', 57 | username: 'Gus', 58 | imageUrl: `${baseImageUrl}/gus.jpg`, 59 | role: 'standard', 60 | }, 61 | { 62 | id: '1f008731-4645-43de-8af9-3060d4086008', 63 | email: 'michael.ehrmantraut@mail.com', 64 | name: 'Michael Ehrmantraut', 65 | username: 'Mike', 66 | imageUrl: `${baseImageUrl}/mike.jpg`, 67 | role: 'standard', 68 | }, 69 | { 70 | id: '1f008731-4645-43de-8af9-3060d4086009', 71 | email: 'hector.salamanca@mail.com', 72 | name: 'Hector Salamanca', 73 | username: 'Tio', 74 | imageUrl: `${baseImageUrl}/tio.jpg`, 75 | role: 'standard', 76 | }, 77 | { 78 | id: '1f008731-4645-43de-8af9-3060d4086010', 79 | email: 'alberto.salamanca@mail.com', 80 | name: 'Alberto Salamanca', 81 | username: 'Tuco', 82 | role: 'standard', 83 | }, 84 | { 85 | id: '1f008731-4645-43de-8af9-3060d4086011', 86 | email: 'gale.boetticher@mail.com', 87 | name: 'Gale Boetticher', 88 | username: 'Captain Nerd', 89 | role: 'apprentice', 90 | }, 91 | ] as const satisfies Users; 92 | -------------------------------------------------------------------------------- /src/utils/sleep/index.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); 2 | -------------------------------------------------------------------------------- /src/utils/types/index.ts: -------------------------------------------------------------------------------- 1 | export type DeepMutable = { -readonly [P in keyof TSourceType]: DeepMutable }; 2 | -------------------------------------------------------------------------------- /src/utils/types/process-env.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-object-type */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | /* eslint-disable @typescript-eslint/no-namespace */ 4 | /* eslint-disable @typescript-eslint/consistent-type-definitions */ 5 | 6 | import { z } from 'zod'; 7 | 8 | const envVariables = z.object({ 9 | NODE_ENV: z.union([z.literal('development'), z.literal('production')]), 10 | PORT: z.string().optional(), 11 | }); 12 | 13 | declare global { 14 | namespace NodeJS { 15 | interface ProcessEnv extends z.infer {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/version/VersionInfo.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Do not edit this file! Values will be replaced at build time. 3 | */ 4 | export const VersionInfo = { 5 | tag: '', 6 | branch: '', 7 | commit: '', 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils/version/index.ts: -------------------------------------------------------------------------------- 1 | import { VersionInfo } from './VersionInfo'; 2 | 3 | type GetVersionInfo = () => string; 4 | 5 | export const getVersionInfo: GetVersionInfo = () => { 6 | if (VersionInfo.tag !== '') { 7 | return VersionInfo.tag; 8 | } 9 | 10 | if (VersionInfo.commit !== '') { 11 | return `${VersionInfo.branch} [${VersionInfo.commit.substring(0, 7)}]`; 12 | } 13 | 14 | return 'Local build'; 15 | }; 16 | -------------------------------------------------------------------------------- /trpc-api-export/builder/index.ts: -------------------------------------------------------------------------------- 1 | export type { AppRouter } from 'trpc/router'; 2 | 3 | // Export user roles array as source of truth for frontend (select component, render list of available roles etc.) 4 | export { USER_ROLES } from 'db/User'; 5 | 6 | // Export any backend type, object, array etc. that should be shared with frontend 7 | type Square = { 8 | shape: 'square'; 9 | size: number; 10 | }; 11 | type Rectangle = { 12 | shape: 'rectangle'; 13 | width: number; 14 | height: number; 15 | }; 16 | export type Shape = Square | Rectangle; 17 | 18 | export const SharedSquareObject: Shape = { 19 | shape: 'square', 20 | size: 50, 21 | }; 22 | -------------------------------------------------------------------------------- /trpc-api-export/builder/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Export tRPC API", 4 | "compilerOptions": { 5 | "declaration": true, 6 | "declarationDir": "dist", 7 | "baseUrl": "../../src", 8 | "paths": { 9 | ".": ["."] 10 | }, 11 | "allowJs": true, 12 | "strict": true, 13 | "strictNullChecks": true, 14 | "pretty": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "skipLibCheck": true, 19 | "esModuleInterop": true, 20 | "module": "ESNext", 21 | "moduleResolution": "Bundler", 22 | "target": "ES2022", 23 | "moduleDetection": "force" 24 | }, 25 | "exclude": ["../../build", "../../node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /trpc-api-export/builder/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | const tsupConfig = defineConfig({ 4 | entry: ['trpc-api-export/builder/index.ts'], 5 | outDir: 'trpc-api-export/dist', 6 | format: ['esm'], 7 | clean: true, 8 | dts: true, 9 | tsconfig: 'trpc-api-export/builder/tsconfig.build.json', 10 | }); 11 | 12 | // eslint-disable-next-line 13 | export default tsupConfig; 14 | -------------------------------------------------------------------------------- /trpc-api-export/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as _trpc_server from '@trpc/server'; 2 | import * as _trpc_server_unstable_core_do_not_import from '@trpc/server/unstable-core-do-not-import'; 3 | 4 | type DeepMutable = { 5 | -readonly [P in keyof TSourceType]: DeepMutable; 6 | }; 7 | 8 | declare const appRouter: _trpc_server_unstable_core_do_not_import.BuiltRouter< 9 | { 10 | ctx: object; 11 | meta: object; 12 | errorShape: _trpc_server_unstable_core_do_not_import.DefaultErrorShape; 13 | transformer: true; 14 | }, 15 | _trpc_server_unstable_core_do_not_import.DecorateCreateRouterOptions<{ 16 | batch: _trpc_server_unstable_core_do_not_import.BuiltRouter< 17 | { 18 | ctx: object; 19 | meta: object; 20 | errorShape: _trpc_server_unstable_core_do_not_import.DefaultErrorShape; 21 | transformer: true; 22 | }, 23 | _trpc_server_unstable_core_do_not_import.DecorateCreateRouterOptions<{ 24 | list: _trpc_server.TRPCQueryProcedure<{ 25 | input: void; 26 | output: DeepMutable<{ 27 | id: string; 28 | title: string; 29 | description: string | null; 30 | purity: number; 31 | weight: number; 32 | producers: { 33 | id: string; 34 | email: string; 35 | name: string; 36 | username: string; 37 | role: 'administrator' | 'apprentice' | 'standard'; 38 | imageUrl?: string | undefined; 39 | }[]; 40 | supplier: { 41 | id: string; 42 | name: string; 43 | description: string | null; 44 | } | null; 45 | }>[]; 46 | }>; 47 | }> 48 | >; 49 | user: _trpc_server_unstable_core_do_not_import.BuiltRouter< 50 | { 51 | ctx: object; 52 | meta: object; 53 | errorShape: _trpc_server_unstable_core_do_not_import.DefaultErrorShape; 54 | transformer: true; 55 | }, 56 | _trpc_server_unstable_core_do_not_import.DecorateCreateRouterOptions<{ 57 | list: _trpc_server.TRPCQueryProcedure<{ 58 | input: void; 59 | output: DeepMutable<{ 60 | id: string; 61 | email: string; 62 | name: string; 63 | username: string; 64 | role: 'administrator' | 'apprentice' | 'standard'; 65 | imageUrl?: string | undefined; 66 | }>[]; 67 | }>; 68 | show: _trpc_server.TRPCQueryProcedure<{ 69 | input: string; 70 | output: 71 | | DeepMutable<{ 72 | id: string; 73 | email: string; 74 | name: string; 75 | username: string; 76 | role: 'administrator' | 'apprentice' | 'standard'; 77 | imageUrl?: string | undefined; 78 | }> 79 | | `User with id:${string} does not exist in database.`; 80 | }>; 81 | destroy: _trpc_server.TRPCMutationProcedure<{ 82 | input: { 83 | id: string; 84 | }; 85 | output: DeepMutable<{ 86 | id: string; 87 | email: string; 88 | name: string; 89 | username: string; 90 | role: 'administrator' | 'apprentice' | 'standard'; 91 | imageUrl?: string | undefined; 92 | }>[]; 93 | }>; 94 | create: _trpc_server.TRPCMutationProcedure<{ 95 | input: { 96 | user: { 97 | email: string; 98 | name: string; 99 | username: string; 100 | role: 'administrator' | 'apprentice' | 'standard'; 101 | imageUrl?: string | undefined; 102 | }; 103 | }; 104 | output: { 105 | id: string; 106 | email: string; 107 | name: string; 108 | username: string; 109 | role: 'administrator' | 'apprentice' | 'standard'; 110 | imageUrl?: string | undefined; 111 | }; 112 | }>; 113 | }> 114 | >; 115 | util: _trpc_server_unstable_core_do_not_import.BuiltRouter< 116 | { 117 | ctx: object; 118 | meta: object; 119 | errorShape: _trpc_server_unstable_core_do_not_import.DefaultErrorShape; 120 | transformer: true; 121 | }, 122 | _trpc_server_unstable_core_do_not_import.DecorateCreateRouterOptions<{ 123 | seedDb: _trpc_server.TRPCMutationProcedure<{ 124 | input: void; 125 | output: 'Database initialized successfully.'; 126 | }>; 127 | }> 128 | >; 129 | }> 130 | >; 131 | type AppRouter = typeof appRouter; 132 | 133 | declare const USER_ROLES: readonly ['administrator', 'apprentice', 'standard']; 134 | 135 | type Square = { 136 | shape: 'square'; 137 | size: number; 138 | }; 139 | type Rectangle = { 140 | shape: 'rectangle'; 141 | width: number; 142 | height: number; 143 | }; 144 | type Shape = Square | Rectangle; 145 | declare const SharedSquareObject: Shape; 146 | 147 | export { type AppRouter, type Shape, SharedSquareObject, USER_ROLES }; 148 | -------------------------------------------------------------------------------- /trpc-api-export/dist/index.js: -------------------------------------------------------------------------------- 1 | // src/db/User.ts 2 | import { z } from 'zod'; 3 | var USER_ROLES = ['administrator', 'apprentice', 'standard']; 4 | var UserConfigSchema = z.object({ 5 | id: z.string(), 6 | email: z.string(), 7 | name: z.string(), 8 | username: z.string(), 9 | imageUrl: z.string().optional(), 10 | role: z.enum(USER_ROLES), 11 | }); 12 | 13 | // trpc-api-export/builder/index.ts 14 | var SharedSquareObject = { 15 | shape: 'square', 16 | size: 50, 17 | }; 18 | export { SharedSquareObject, USER_ROLES }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "baseUrl": "src", 6 | "paths": { 7 | ".": ["."] 8 | }, 9 | "allowJs": true, 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "pretty": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "esModuleInterop": true, 19 | "module": "ESNext", 20 | "moduleResolution": "Bundler", 21 | "target": "ES2022", 22 | "moduleDetection": "force" 23 | }, 24 | "include": [".", ".eslintrc.cjs", ".commitlintrc.ts"], 25 | "exclude": ["build", "node_modules"] 26 | } 27 | --------------------------------------------------------------------------------