prisma-trpc-generator
v3.0.1
Published
Prisma 2+ generator to emit fully implemented tRPC routers
Maintainers
Readme
⚡ Prisma tRPC Generator
Automatically generate fully implemented, type-safe tRPC routers from your Prisma schema.
🎯 Zero‑config • 🛡️ Type‑safe • ⚡ Fast • 🔧 Customizable
✨ Key features
- 🚀 Zero configuration defaults
- 🔄 Always in sync with your Prisma schema
- 🛡️ 100% TypeScript type-safety
- 🎯 Complete CRUD coverage for all Prisma operations
- ⚙️ Highly configurable: paths, middleware, shield, options
- 📦 Lightweight and fast generation
- 🔗 Integrates with Zod, tRPC Shield, and custom middleware
📚 Table of contents
- 🚀 Quick start
- ⚙️ Configuration
- 🔎 Feature guide
- 📋 Generated output
- 🛠️ Advanced usage
- 🧪 Troubleshooting, performance, FAQ
- 🤝 Contributing
- 📄 License
- 🔗 Related projects
- 🙏 Acknowledgments
🚀 Quick start
Requirements
| Component | Minimum | Recommended | | ---------- | ------- | ----------- | | Node.js | 20.19.0 | 22.x | | Prisma | 7.0.0 | Latest 7.x | | TypeScript | 5.4.0 | 5.9.x |
Install
# npm
npm install prisma-trpc-generator
# yarn
yarn add prisma-trpc-generator
# pnpm
pnpm add prisma-trpc-generatorConfigure Prisma 7
Create
prisma.config.tsat the repo root:import 'dotenv/config'; import { defineConfig, env } from 'prisma/config'; export default defineConfig({ schema: 'prisma/schema.prisma', migrations: { path: 'prisma/migrations', seed: 'tsx prisma/seed.ts', }, datasource: { url: env('DATABASE_URL'), }, });Update your
generator clientblock:generator client { provider = "prisma-client" output = "../node_modules/.prisma/client" }Set
DATABASE_URL(e.g.,file:./prisma/dev.db) in.envand instantiatePrismaClientwith the adapter that matches your database (SQLite →@prisma/adapter-better-sqlite3, Postgres →@prisma/adapter-pg, etc.).
Minimal setup
Add the generator to your Prisma schema and point to your JSON config file:
generator trpc {
provider = "node ./lib/generator.js"
output = "./prisma/generated"
config = "./prisma/trpc.config.json"
}Create prisma/trpc.config.json (see Feature guide for options), enable "strict": true in tsconfig.json, then generate:
npx prisma generate⚙️ Configuration
As of v2.x, configuration is unified via a single JSON file. Your Prisma generator block should only specify output and config.
Example prisma/trpc.config.json:
{
"withZod": true,
"withMiddleware": true,
"withShield": "./shield",
"contextPath": "./context",
"trpcOptionsPath": "./trpcOptions",
"dateTimeStrategy": "date",
"withMeta": false,
"postman": true,
"postmanExamples": "skeleton",
"openapi": true,
"withRequestId": false,
"withLogging": false,
"withServices": false
}Notes
- The config path is resolved relative to the Prisma schema file.
- Aliases
configPathandconfigFileare also accepted. - If a config file is provided, any inline options in the generator block are ignored with a warning.
- Inline options without a config file still work for now but are deprecated and will be removed in a future major release.
🔎 Feature guide
Each feature is opt‑in via the JSON config. Below are concise how‑tos and the exact keys to set.
1) Zod validation (inputs)
- Key:
withZod: true - Emits
schemas/with Zod types for procedure inputs; routers wire.input()automatically. - Date handling: Set
dateTimeStrategyto control DateTime field validation:"date"(default):z.date()- accepts only Date objects"coerce":z.coerce.date()- accepts both Date objects and ISO strings"isoString": ISO string validation with transformation
Extending Zod schemas with Prisma comments
You can add additional Zod validation constraints using special comments in your Prisma schema:
model User {
id Int @id @default(autoincrement()) /// @zod.number.int()
email String @unique /// @zod.string.email()
name String? /// @zod.string.min(1).max(100)
age Int? /// @zod.number.int().min(0).max(120)
posts Post[]
}
model Post {
id Int @id @default(autoincrement()) /// @zod.number.int()
title String /// @zod.string.min(1).max(255, { message: "Title must be shorter than 256 characters" })
content String? /// @zod.string.max(10000)
published Boolean @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}This generates Zod schemas with the specified validations:
export const UserCreateInput = z.object({
id: z.number().int(),
email: z.string().email(),
name: z.string().min(1).max(100).nullish(),
age: z.number().int().min(0).max(120).nullish(),
// ...
});For more advanced Zod validation options and syntax, see the prisma-zod-generator documentation.
2) Middleware & Shield
- Keys:
withMiddleware: boolean | string,withShield: boolean | string - When
withMiddleware: true, a basic middleware scaffold is included; or point to your own path string. - When
withShieldis truthy, the generator imports yourpermissionsand exposesshieldedProcedureincreateRouter.ts.
3) Auth (session / JWT / custom)
- Key:
auth: boolean | { strategy?: 'session'|'jwt'|'custom'; rolesField?: string; jwt?: {...}; session?: {...}; custom?: {...} } - When enabled, generator emits:
routers/helpers/auth-strategy.ts(stubs + default HS256 JWT verifier)routers/helpers/auth.tswithensureAuthandensureRolecreateRouter.tswiresauthMiddleware,publicProcedure,protectedProcedure,roleProcedure(roles)
- See
docs/usage/auth.mdfor strategy hooks and examples.
4) Request ID + logging
- Keys:
withRequestId: boolean,withLogging: boolean - Adds a small requestId middleware and optional structured log line around every procedure.
- To propagate requestId into errors, return it in your
trpcOptions.errorFormatter.
5) tRPC Metadata Support
- Key:
withMeta: boolean | { openapi?: boolean; auth?: boolean; description?: boolean; defaultMeta?: object } - When enabled, adds
.meta()calls to generated procedures with:- OpenAPI-compatible metadata (HTTP methods, paths, tags, descriptions)
- Authentication metadata for middleware integration
- Custom metadata via
defaultMetaconfiguration
- Perfect for OpenAPI documentation, conditional auth, and enhanced middleware
6) OpenAPI (MVP)
- Key:
openapi: boolean | { enabled?: boolean; title?: string; version?: string; baseUrl?: string; pathPrefix?: string; pathStyle?: 'slash'|'dot'; includeExamples?: boolean } - Emits
openapi/openapi.jsonandrouters/adapters/openapi.tswith a tagged document. - Paths map to tRPC endpoints (POST) with a
{ input: {} }request body schema and optional skeleton examples.
7) Postman collection
- Key:
postman: boolean | { endpoint?: string; envName?: string; fromOpenApi?: boolean; examples?: 'none'|'skeleton' } - Emits
postman/collection.json. WhenfromOpenApi: true, the collection is derived from OpenAPI. - Set
examples: 'skeleton'to include sample bodies for common operations.
8) DDD services (optional)
- Keys:
withServices,serviceStyle,serviceDir,withListMethod,serviceImports - Emits a BaseService and per‑model service stubs; routers can delegate to services when enabled.
- Tenancy/soft‑delete helpers are included in the service layer if you choose to use it.
Migration from inline config
- Create
prisma/trpc.config.jsonand move all previous inline keys into it. - Replace keys in
generator trpcso it only containsoutputandconfig. - Run generation. If you still have inline keys, the generator will ignore them and warn.
📋 Generated output
For the following schema:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}The generator creates:

generated/
├── routers/
│ ├── index.ts # Main app router combining all model routers
│ ├── helpers/
│ │ └── createRouter.ts # Base router factory with middleware/shield setup
│ ├── User.router.ts # User CRUD operations
│ └── Post.router.ts # Post CRUD operations
└── schemas/ # Zod validation schemas (if withZod: true)
├── objects/ # Input type schemas
├── findManyUser.schema.ts
├── createOneUser.schema.ts
└── index.ts # Barrel exports🛠️ Advanced usage
Custom middleware
// src/middleware.ts
import { TRPCError } from '@trpc/server';
import { t } from './trpc';
export const authMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
...ctx,
user: ctx.user,
},
});
});
export const loggingMiddleware = t.middleware(async ({ path, type, next }) => {
console.log(`tRPC ${type} ${path}`);
return next();
});Integration with tRPC Shield
// src/permissions.ts
import { shield, rule, and } from 'trpc-shield';
const isAuthenticated = rule()(async (_parent, _args, ctx) => !!ctx.user);
const isOwner = rule()(async (_parent, args, ctx) => {
if (!args.where?.id) return false;
const post = await ctx.prisma.post.findUnique({
where: { id: args.where.id },
select: { authorId: true },
});
return post?.authorId === ctx.user?.id;
});
export const permissions = shield({
query: {
findManyPost: true, // Public
findUniqueUser: isAuthenticated,
},
mutation: {
createOnePost: isAuthenticated,
updateOnePost: and(isAuthenticated, isOwner),
deleteOnePost: and(isAuthenticated, isOwner),
},
});Custom tRPC options
// src/trpcOptions.ts
import { ZodError } from 'zod';
import superjson from 'superjson';
export default {
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
};🎨 Customizations
Skipping models
/// @@Gen.model(hide: true)
model InternalLog {
id Int @id @default(autoincrement())
message String
createdAt DateTime @default(now())
}Custom context
// src/context.ts
import 'dotenv/config';
import { PrismaClient } from '@prisma/client';
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
const adapter = new PrismaBetterSqlite3({
url: process.env.DATABASE_URL as ':memory:' | (string & {}),
});
const prisma = new PrismaClient({ adapter });
export interface Context {
prisma: PrismaClient;
user?: { id: string; email: string; role: string };
}
export const createContext = async ({ req }): Promise<Context> => {
const user = await getUserFromRequest(req);
return { prisma, user };
};📚 Examples
Basic CRUD with authentication
// src/server/routers/posts.ts
import { z } from 'zod';
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
export const postsRouter = createTRPCRouter({
getAll: publicProcedure.query(({ ctx }) =>
ctx.prisma.post.findMany({
where: { published: true },
include: { author: { select: { name: true } } },
}),
),
create: protectedProcedure
.input(
z.object({ title: z.string().min(1), content: z.string().optional() }),
)
.mutation(({ ctx, input }) =>
ctx.prisma.post.create({ data: { ...input, authorId: ctx.user.id } }),
),
update: protectedProcedure
.input(
z.object({
id: z.number(),
title: z.string().min(1).optional(),
content: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
const { id, ...data } = input;
const post = await ctx.prisma.post.findFirst({
where: { id, authorId: ctx.user.id },
});
if (!post) throw new TRPCError({ code: 'FORBIDDEN' });
return ctx.prisma.post.update({ where: { id }, data });
}),
});Next.js App Router integration
// src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '@/server/api/root';
import { createContext } from '@/server/api/context';
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
});
export { handler as GET, handler as POST };Client-side usage
// src/lib/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '@/server/api/root';
export const trpc = createTRPCReact<AppRouter>();
const PostList = () => {
const { data: posts, isLoading } = trpc.post.findMany.useQuery();
const createPost = trpc.post.createOne.useMutation();
if (isLoading) return <div>Loading...</div>;
return (
<div>
{posts?.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
};🔍 Troubleshooting, performance, FAQ
Common issues
Error: Cannot find module '../context'
- Ensure your
contextPathis correct relative to the output directory. - Check that your context file exports a
Contexttype.
TypeScript errors in generated routers
- Ensure dependencies are installed and up to date.
- Verify your tRPC context is properly typed.
- Ensure
strict: trueis enabled intsconfig.json.
Generated routers not updating
- Run
npx prisma generateafter modifying your schema. - Check that the generator is properly configured in
schema.prisma. - Clear your build cache and regenerate.
Zod validation errors
- Ensure Zod 4.0+ is installed.
- Check that input schemas match your Prisma model types.
- For DateTime validation errors with JSON APIs, set
dateTimeStrategy: "coerce"to accept date strings.
Performance considerations
For large schemas (50+ models):
- Use selective generation with model hiding.
- Split routers into multiple files.
- Consider lazy loading routers.
Build times:
- Add generated files to
.gitignore. - Use parallel builds where possible.
- Cache dependencies in CI.
FAQ
Q: Can I customize the generated router validation rules? A: Routers are generated based on your Prisma schema constraints; change your Prisma model definitions to affect validation.
Q: Does this work with Prisma Edge Runtime? A: Yes.
Q: What databases are supported? A: All Prisma‑compatible databases.
Q: How are enums handled? A: Enums are converted to Zod enums and included in validation.
Q: Can I exclude fields from validation?
A: Use Prisma's @ignore or @@Gen.model(hide: true).
Getting help
- 🐛 Bug reports: https://github.com/omar-dulaimi/prisma-trpc-generator/issues/new
- 💡 Feature requests: https://github.com/omar-dulaimi/prisma-trpc-generator/issues/new
- 💬 Discussions: https://github.com/omar-dulaimi/prisma-trpc-generator/discussions
🤝 Contributing
Development setup
- Fork and clone the repository
git clone https://github.com/your-username/prisma-trpc-generator.git
cd prisma-trpc-generator- Install dependencies (requires Node.js 20.19.0+; 22.x recommended, this repo uses pnpm)
pnpm install- Build/generate
pnpm run generate- Run tests
pnpm testTesting
- Unit tests: core transformation logic
- Integration tests: end‑to‑end router generation
- Multi‑provider tests: all database providers
- Performance tests: large schema handling
Run specific test suites
pnpm test --silent
pnpm run test:integration
pnpm run test:coverage
pnpm run test:comprehensiveContribution guidelines
- Create an issue for bugs or feature requests.
- Follow the existing code style (ESLint + Prettier).
- Add tests for new functionality.
- Update documentation as needed.
- Submit a PR with a clear description.
Code style
pnpm run lint
pnpm run formatRelease process
Semantic versioning
- Patch: bug fixes and small improvements
- Minor: new features and enhancements
- Major: breaking changes
📄 License
This project is licensed under the MIT License — see the LICENSE file for details.
🔗 Related projects
- prisma-zod-generator — Generate Zod schemas from Prisma schema
- prisma-trpc-shield-generator — Generate tRPC Shield permissions from Prisma schema
- tRPC Shield — Permission system for tRPC
- Prisma — Database toolkit and ORM
- tRPC — End‑to‑end typesafe APIs made easy
🙏 Acknowledgments
- Prisma — Modern database toolkit
- tRPC — End‑to‑end typesafe APIs
- Zod — TypeScript‑first schema validation
- All contributors
