prisma-generator-express 1.38.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +524 -163
- package/dist/constants.d.ts +1 -1
- package/dist/generators/generateHonoHandler.d.ts +4 -0
- package/dist/generators/generateHonoHandler.js +94 -0
- package/dist/generators/generateHonoHandler.js.map +1 -0
- package/dist/generators/generateRouterHono.d.ts +6 -0
- package/dist/generators/generateRouterHono.js +368 -0
- package/dist/generators/generateRouterHono.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.js +50 -4
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.js +55 -2
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +27 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/constants.ts +1 -1
- package/src/copy/routeConfig.hono.ts +21 -0
- package/src/generators/generateHonoHandler.ts +104 -0
- package/src/generators/generateRouterHono.ts +380 -0
- package/src/generators/generateUnifiedDocs.ts +52 -4
- package/src/generators/generateUnifiedScalarUI.ts +56 -2
- package/src/index.ts +28 -11
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://codecov.io/gh/multipliedtwice/prisma-generator-express)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
Prisma generator that creates Express or
|
|
8
|
+
Prisma generator that creates Express, Fastify, or Hono CRUD API routes with OpenAPI documentation from your Prisma schema.
|
|
9
9
|
|
|
10
10
|
Running `npx prisma generate` produces:
|
|
11
11
|
|
|
@@ -17,15 +17,17 @@ Running `npx prisma generate` produces:
|
|
|
17
17
|
- Client-side query parameter encoder
|
|
18
18
|
- Guard/variant shape enforcement via prisma-guard integration
|
|
19
19
|
|
|
20
|
-
Supports
|
|
20
|
+
Supports **Express**, **Fastify**, and **Hono** targets via the `target` configuration option.
|
|
21
21
|
|
|
22
22
|
## Table of contents
|
|
23
23
|
|
|
24
24
|
- [Compatibility](#compatibility)
|
|
25
25
|
- [Installation](#installation)
|
|
26
26
|
- [Setup](#setup)
|
|
27
|
+
- [Path casing in generated endpoints](#path-casing-in-generated-endpoints)
|
|
27
28
|
- [Usage (Express)](#usage-express)
|
|
28
29
|
- [Usage (Fastify)](#usage-fastify)
|
|
30
|
+
- [Usage (Hono)](#usage-hono)
|
|
29
31
|
- [Selective routes with middleware](#selective-routes-with-middleware)
|
|
30
32
|
- [Guard shapes (prisma-guard integration)](#guard-shapes-prisma-guard-integration)
|
|
31
33
|
- [Request body format](#request-body-format)
|
|
@@ -64,6 +66,9 @@ Some operations require newer versions:
|
|
|
64
66
|
| --------- | ------------ | ---------------- |
|
|
65
67
|
| Express | `"express"` | `express.Router()` factory function per model |
|
|
66
68
|
| Fastify | `"fastify"` | Fastify plugin function per model |
|
|
69
|
+
| Hono | `"hono"` | `Hono` instance factory function per model |
|
|
70
|
+
|
|
71
|
+
The Hono target v1 is tested on Node.js runtimes only. See [Cloudflare Workers and edge runtimes](#cloudflare-workers-and-edge-runtimes).
|
|
67
72
|
|
|
68
73
|
### Database provider support
|
|
69
74
|
|
|
@@ -95,12 +100,18 @@ Peer dependencies for Fastify:
|
|
|
95
100
|
npm install @prisma/client fastify
|
|
96
101
|
```
|
|
97
102
|
|
|
103
|
+
Peer dependencies for Hono:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm install @prisma/client hono
|
|
107
|
+
```
|
|
108
|
+
|
|
98
109
|
Optional peer dependencies:
|
|
99
110
|
|
|
100
111
|
```bash
|
|
101
112
|
npm install prisma-sql # SQL optimization
|
|
102
113
|
npm install prisma-guard zod # Guard shape enforcement
|
|
103
|
-
npm install prisma-query-builder-ui # Visual query playground
|
|
114
|
+
npm install prisma-query-builder-ui # Visual query playground (Express/Fastify only — not auto-started for Hono)
|
|
104
115
|
```
|
|
105
116
|
|
|
106
117
|
## Setup
|
|
@@ -117,7 +128,7 @@ generator express {
|
|
|
117
128
|
}
|
|
118
129
|
```
|
|
119
130
|
|
|
120
|
-
To
|
|
131
|
+
To target Fastify or Hono, set the `target` config:
|
|
121
132
|
|
|
122
133
|
```prisma
|
|
123
134
|
generator express {
|
|
@@ -126,7 +137,14 @@ generator express {
|
|
|
126
137
|
}
|
|
127
138
|
```
|
|
128
139
|
|
|
129
|
-
|
|
140
|
+
```prisma
|
|
141
|
+
generator express {
|
|
142
|
+
provider = "prisma-generator-express"
|
|
143
|
+
target = "hono"
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Valid `target` values are `"express"` (default), `"fastify"`, and `"hono"`.
|
|
130
148
|
|
|
131
149
|
The generator detects the Prisma client generator automatically. All standard provider values are supported: `prisma-client-js`, `@prisma/client`, and `prisma-client`.
|
|
132
150
|
|
|
@@ -136,6 +154,26 @@ Generate:
|
|
|
136
154
|
npx prisma generate
|
|
137
155
|
```
|
|
138
156
|
|
|
157
|
+
## Path casing in generated endpoints
|
|
158
|
+
|
|
159
|
+
Model names are converted to **flat lowercase** in URL paths. There is no kebab-case or snake_case conversion — the model name is lowercased character by character.
|
|
160
|
+
|
|
161
|
+
| Model name | URL path |
|
|
162
|
+
| ----------------- | -------------------- |
|
|
163
|
+
| `User` | `/user` |
|
|
164
|
+
| `BlogPost` | `/blogpost` |
|
|
165
|
+
| `OrderItem` | `/orderitem` |
|
|
166
|
+
| `INVOICE_RECORDS` | `/invoice_records` |
|
|
167
|
+
| `apiKey` | `/apikey` |
|
|
168
|
+
|
|
169
|
+
Underscores in model names are preserved. Camel-case word boundaries are not preserved.
|
|
170
|
+
|
|
171
|
+
Throughout this README, `{modelname}` (lowercase) represents the converted path segment. For example, the path `/{modelname}/first` refers to `/user/first` for a `User` model, or `/blogpost/first` for a `BlogPost` model.
|
|
172
|
+
|
|
173
|
+
The generated directory structure preserves the original model casing — e.g. `generated/BlogPost/BlogPostRouter.ts` — but the runtime URL is `/blogpost`.
|
|
174
|
+
|
|
175
|
+
To remove the model prefix entirely, set `addModelPrefix: false` in the route config. To replace it with a custom prefix, use `customUrlPrefix`.
|
|
176
|
+
|
|
139
177
|
## Usage (Express)
|
|
140
178
|
|
|
141
179
|
```ts
|
|
@@ -146,6 +184,8 @@ import { UserRouter } from './generated/User/UserRouter'
|
|
|
146
184
|
const prisma = new PrismaClient()
|
|
147
185
|
const app = express()
|
|
148
186
|
|
|
187
|
+
app.use(express.json())
|
|
188
|
+
|
|
149
189
|
app.use((req, res, next) => {
|
|
150
190
|
req.prisma = prisma
|
|
151
191
|
next()
|
|
@@ -162,6 +202,8 @@ app.listen(3000, () => {
|
|
|
162
202
|
})
|
|
163
203
|
```
|
|
164
204
|
|
|
205
|
+
`express.json()` is required because write endpoints (`create`, `update`, `delete`, `upsert`) and POST read endpoints accept JSON request bodies.
|
|
206
|
+
|
|
165
207
|
## Usage (Fastify)
|
|
166
208
|
|
|
167
209
|
When `target = "fastify"`, each model produces a Fastify plugin function instead of an Express router.
|
|
@@ -195,16 +237,138 @@ fastify.listen({ port: 3000 }, () => {
|
|
|
195
237
|
|
|
196
238
|
The generated function signature is `async function ModelRoutes(fastify: FastifyInstance, config?: RouteConfig)`. It registers routes directly on the provided Fastify instance.
|
|
197
239
|
|
|
198
|
-
|
|
240
|
+
## Usage (Hono)
|
|
241
|
+
|
|
242
|
+
When `target = "hono"`, each model produces a function that returns a Hono instance.
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { Hono } from 'hono'
|
|
246
|
+
import { PrismaClient } from '@prisma/client'
|
|
247
|
+
import { UserRouter } from './generated/User/UserRouter'
|
|
248
|
+
|
|
249
|
+
type Env = {
|
|
250
|
+
Variables: {
|
|
251
|
+
prisma: PrismaClient
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const prisma = new PrismaClient()
|
|
256
|
+
const app = new Hono<Env>()
|
|
257
|
+
|
|
258
|
+
app.use('*', async (c, next) => {
|
|
259
|
+
c.set('prisma', prisma)
|
|
260
|
+
await next()
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const userConfig = {
|
|
264
|
+
enableAll: true,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
app.route('/', UserRouter(userConfig))
|
|
268
|
+
|
|
269
|
+
export default app
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The generated function signature is `UserRouter(config?: RouteConfig): Hono`. Mount with `app.route(prefix, UserRouter(config))`.
|
|
273
|
+
|
|
274
|
+
PrismaClient is injected via `c.set('prisma', prismaInstance)` in middleware that runs before the router. Declare `prisma` (and any optional connectors like `postgres` / `sqlite`) in your Hono app's `Variables` type so TypeScript can verify the injection. The same pattern applies to optional `postgres` / `sqlite` connectors for [prisma-sql integration](#prisma-sql-integration).
|
|
275
|
+
|
|
276
|
+
### Hooks (Hono)
|
|
277
|
+
|
|
278
|
+
Hono hooks are native Hono middleware functions:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import type { HonoHookHandler } from './generated/routeConfig.target'
|
|
282
|
+
|
|
283
|
+
const auth: HonoHookHandler = async (c, next) => {
|
|
284
|
+
const token = c.req.header('authorization')
|
|
285
|
+
if (!token) return c.json({ message: 'Unauthorized' }, 401)
|
|
286
|
+
await next()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const userConfig = {
|
|
290
|
+
findMany: {
|
|
291
|
+
before: [auth],
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Call `await next()` to continue the chain. Return a `Response` to short-circuit — subsequent hooks, the main handler, and the response middleware will not run.
|
|
297
|
+
|
|
298
|
+
### HTTPException normalization
|
|
299
|
+
|
|
300
|
+
Throwing Hono's `HTTPException` from a hook short-circuits to a JSON error response. The router's `app.onError` catches the exception, preserves the status code, and **normalizes the response body** to `{ "message": err.message }`.
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { HTTPException } from 'hono/http-exception'
|
|
304
|
+
|
|
305
|
+
const auth: HonoHookHandler = async (c, next) => {
|
|
306
|
+
const token = c.req.header('authorization')
|
|
307
|
+
if (!token) {
|
|
308
|
+
throw new HTTPException(401, { message: 'Unauthorized' })
|
|
309
|
+
}
|
|
310
|
+
await next()
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Custom response bodies attached to `HTTPException` are **not preserved** — the router always returns `{ message: err.message }` with the exception's status code. If you need a custom response body, return a `Response` directly from the hook instead of throwing.
|
|
315
|
+
|
|
316
|
+
This normalization ensures all errors from generated routes share a single shape, so clients only need to handle one error format.
|
|
317
|
+
|
|
318
|
+
### Cloudflare Workers and edge runtimes
|
|
319
|
+
|
|
320
|
+
The Hono target v1 is tested on Node.js runtimes only. The route layer may be portable to edge runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge), but **production edge support is not guaranteed**. Prisma Client edge usage requires compatible Prisma setup, driver adapters, or Prisma Accelerate / Prisma Postgres depending on the database. `prisma-guard` edge compatibility is unverified.
|
|
321
|
+
|
|
322
|
+
On Cloudflare Workers, you must construct an edge-compatible Prisma client yourself and expose it through your runtime environment. Cloudflare does not provide a built-in Prisma binding — the exact setup depends on your database and Prisma adapter (Prisma Accelerate, `@prisma/adapter-d1`, etc.).
|
|
323
|
+
|
|
324
|
+
A minimal pattern, assuming you've already wired up an edge-compatible client behind a `PRISMA` binding:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
type Env = {
|
|
328
|
+
Bindings: {
|
|
329
|
+
PRISMA: any
|
|
330
|
+
}
|
|
331
|
+
Variables: {
|
|
332
|
+
prisma: any
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const app = new Hono<Env>()
|
|
337
|
+
|
|
338
|
+
app.use('*', async (c, next) => {
|
|
339
|
+
c.set('prisma', c.env.PRISMA)
|
|
340
|
+
await next()
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
app.route('/', UserRouter({ enableAll: true }))
|
|
344
|
+
|
|
345
|
+
export default app
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Both `Bindings` (what the runtime injects) and `Variables` (what your middleware sets via `c.set`) need to be declared on the app's `Env` type.
|
|
349
|
+
|
|
350
|
+
### Query Builder
|
|
351
|
+
|
|
352
|
+
The Query Builder playground is Node-only and **not auto-started** by the Hono target. The generated `?ui=playground` route can render the playground iframe, but the Hono router does not start the Query Builder server. Start `prisma-query-builder-ui` manually in a separate process and point the config to that server when needed.
|
|
353
|
+
|
|
354
|
+
### Query string differences
|
|
355
|
+
|
|
356
|
+
Hono's `c.req.query()` returns a flat `Record<string, string>` — duplicate query keys collapse to the last value. For example, `?take=10&take=20` becomes `{ take: '20' }`. This differs from Express, which parses `?a=1&a=2` into `{ a: ['1', '2'] }`.
|
|
357
|
+
|
|
358
|
+
The `encodeQueryParams` client utility does not emit duplicate keys, so this only matters for hand-built query strings. All complex Prisma arguments are JSON-encoded into single query values.
|
|
359
|
+
|
|
360
|
+
### Key differences between targets
|
|
199
361
|
|
|
200
|
-
| Aspect | Express | Fastify |
|
|
201
|
-
| ------ | ------- | ------- |
|
|
202
|
-
| Generated function | `ModelRouter(config)` returns `express.Router` | `ModelRoutes(fastify, config)` registers on instance |
|
|
203
|
-
| Mounting | `app.use('/', ModelRouter(config))` | `fastify.register(async (i) => { await ModelRoutes(i, config) })` |
|
|
204
|
-
| Hook types | `
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
362
|
+
| Aspect | Express | Fastify | Hono |
|
|
363
|
+
| ------ | ------- | ------- | ---- |
|
|
364
|
+
| Generated function | `ModelRouter(config)` returns `express.Router` | `ModelRoutes(fastify, config)` registers on instance | `ModelRouter(config)` returns `Hono` instance |
|
|
365
|
+
| Mounting | `app.use('/', ModelRouter(config))` | `fastify.register(async (i) => { await ModelRoutes(i, config) })` | `app.route('/', ModelRouter(config))` |
|
|
366
|
+
| Hook types | `RequestHandler[]` | `FastifyHookHandler[]` | `HonoHookHandler[]` (native middleware) |
|
|
367
|
+
| Hook signature | `(req, res, next)` | `(request, reply)` | `(c, next)` |
|
|
368
|
+
| Guard resolveVariant | `express.Request` | `FastifyRequest` | Hono `Context` |
|
|
369
|
+
| PrismaClient injection | `req.prisma = prisma` | `request.prisma = prisma` | `c.set('prisma', prisma)` |
|
|
370
|
+
| Error handling | Express error middleware | `setErrorHandler` | `app.onError` |
|
|
371
|
+
| Query Builder auto-start | Yes (Node only) | Yes (Node only) | No (manual start) |
|
|
208
372
|
|
|
209
373
|
## Selective routes with middleware
|
|
210
374
|
|
|
@@ -242,15 +406,33 @@ fastify.register(async (instance) => {
|
|
|
242
406
|
})
|
|
243
407
|
```
|
|
244
408
|
|
|
245
|
-
Only operations listed in the config (or all when `enableAll: true`) are registered. Operations not listed produce no routes.
|
|
246
|
-
|
|
247
409
|
Fastify hooks receive `(request: FastifyRequest, reply: FastifyReply)`. If a hook sends a reply (via `reply.send()`), subsequent hooks and the handler are skipped.
|
|
248
410
|
|
|
411
|
+
### Hono
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
const userConfig = {
|
|
415
|
+
findMany: {
|
|
416
|
+
before: [async (c, next) => { /* auth check */ await next() }],
|
|
417
|
+
},
|
|
418
|
+
create: {
|
|
419
|
+
before: [async (c, next) => { /* auth + validation */ await next() }],
|
|
420
|
+
},
|
|
421
|
+
findUnique: {},
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
app.route('/', UserRouter(userConfig))
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Hono hooks are native middleware functions. Call `await next()` to continue the chain. Return a `Response` (e.g. `c.json({...}, 403)`) or throw `HTTPException` to short-circuit — subsequent hooks and the handler will not run.
|
|
428
|
+
|
|
429
|
+
Only operations listed in the config (or all when `enableAll: true`) are registered. Operations not listed produce no routes.
|
|
430
|
+
|
|
249
431
|
## Guard shapes (prisma-guard integration)
|
|
250
432
|
|
|
251
433
|
prisma-generator-express integrates with [prisma-guard](https://github.com/multipliedtwice/prisma-guard) to enforce input validation, query shape restrictions, and tenant isolation on generated routes. When a `shape` is configured on an operation, the handler calls `prisma.model.guard(shape, caller).method(args)` instead of `prisma.model.method(args)`.
|
|
252
434
|
|
|
253
|
-
Guard shapes work identically
|
|
435
|
+
Guard shapes work identically across all three targets. The only difference is the type of the `resolveVariant` callback parameter (`Request` for Express, `FastifyRequest` for Fastify, `Context` for Hono).
|
|
254
436
|
|
|
255
437
|
### Guard setup
|
|
256
438
|
|
|
@@ -275,7 +457,7 @@ generator express {
|
|
|
275
457
|
}
|
|
276
458
|
```
|
|
277
459
|
|
|
278
|
-
Run `npx prisma generate` to emit both the
|
|
460
|
+
Run `npx prisma generate` to emit both the routes and the guard artifacts.
|
|
279
461
|
|
|
280
462
|
Extend PrismaClient with the guard extension and attach it to requests:
|
|
281
463
|
|
|
@@ -293,6 +475,8 @@ const prisma = new PrismaClient().$extends(
|
|
|
293
475
|
|
|
294
476
|
const app = express()
|
|
295
477
|
|
|
478
|
+
app.use(express.json())
|
|
479
|
+
|
|
296
480
|
app.use((req, res, next) => {
|
|
297
481
|
req.prisma = prisma
|
|
298
482
|
next()
|
|
@@ -301,8 +485,10 @@ app.use((req, res, next) => {
|
|
|
301
485
|
app.use('/', UserRouter({
|
|
302
486
|
findMany: {
|
|
303
487
|
shape: {
|
|
304
|
-
|
|
305
|
-
|
|
488
|
+
default: {
|
|
489
|
+
where: { name: { contains: true } },
|
|
490
|
+
take: { max: 50, default: 20 },
|
|
491
|
+
},
|
|
306
492
|
},
|
|
307
493
|
},
|
|
308
494
|
}))
|
|
@@ -310,46 +496,62 @@ app.use('/', UserRouter({
|
|
|
310
496
|
app.listen(3000)
|
|
311
497
|
```
|
|
312
498
|
|
|
499
|
+
For Fastify and Hono, attach the extended client the same way — via `request.prisma = prisma` (Fastify) or `c.set('prisma', prisma)` (Hono).
|
|
500
|
+
|
|
313
501
|
If prisma-guard is not installed or the client is not extended with the guard extension, requests to guarded routes return 500 with the message: `Guard shapes require prisma-guard extension on PrismaClient. Install: npm install prisma-guard, then extend your client with guardExtension().`
|
|
314
502
|
|
|
315
503
|
### How guard integration works
|
|
316
504
|
|
|
317
505
|
Each operation config accepts an optional `shape` property. When present, the generated handler:
|
|
318
506
|
|
|
319
|
-
1. Stores the shape on the request context via middleware (Express: `res.locals.guardShape`, Fastify: `request.guardShape`)
|
|
507
|
+
1. Stores the shape on the request context via middleware (Express: `res.locals.guardShape = shape`, Fastify: `request.guardShape = shape`, Hono: `c.set('guardShape', shape)`)
|
|
320
508
|
2. Resolves the caller from `config.guard.resolveVariant(req)`, then from the configured header (default `x-api-variant`), falling back to `undefined`
|
|
321
509
|
3. Calls `prisma.model.guard(shape, caller).method(args)` instead of `prisma.model.method(args)`
|
|
322
510
|
|
|
511
|
+
The downstream handler reads these values (`res.locals.guardShape`, `request.guardShape`, `c.get('guardShape')`) when constructing the Prisma call.
|
|
512
|
+
|
|
323
513
|
When `shape` is absent, the handler calls Prisma directly with no guard enforcement.
|
|
324
514
|
|
|
325
|
-
|
|
515
|
+
Generated route config types treat `shape` as a named shape map. Use `default` for the normal single-shape case, and add other keys only when you need caller-based variants. The runtime still passes the map to `prisma-guard`; the `default` variant is selected when no caller is provided or no variant matches.
|
|
516
|
+
|
|
517
|
+
### Default shape per operation
|
|
518
|
+
|
|
519
|
+
In generated route configs, `shape` is always a named shape map. Use the `default` key when an operation has one normal shape and no caller-specific variants.
|
|
326
520
|
|
|
327
|
-
|
|
521
|
+
`default` is used when no caller is provided or when the caller does not match a named variant. If you do not want fallback behavior, omit `default` and define only explicit variants.
|
|
328
522
|
|
|
329
523
|
```ts
|
|
330
524
|
const userConfig = {
|
|
331
525
|
findMany: {
|
|
332
526
|
shape: {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
527
|
+
default: {
|
|
528
|
+
where: { email: { contains: true }, role: { equals: true } },
|
|
529
|
+
orderBy: { createdAt: true },
|
|
530
|
+
take: { max: 100, default: 25 },
|
|
531
|
+
skip: true,
|
|
532
|
+
},
|
|
337
533
|
},
|
|
338
534
|
},
|
|
339
535
|
create: {
|
|
340
536
|
shape: {
|
|
341
|
-
|
|
537
|
+
default: {
|
|
538
|
+
data: { email: true, name: true, role: 'user' },
|
|
539
|
+
},
|
|
342
540
|
},
|
|
343
541
|
},
|
|
344
542
|
update: {
|
|
345
543
|
shape: {
|
|
346
|
-
|
|
347
|
-
|
|
544
|
+
default: {
|
|
545
|
+
data: { name: true },
|
|
546
|
+
where: { id: { equals: true } },
|
|
547
|
+
},
|
|
348
548
|
},
|
|
349
549
|
},
|
|
350
550
|
delete: {
|
|
351
551
|
shape: {
|
|
352
|
-
|
|
552
|
+
default: {
|
|
553
|
+
where: { id: { equals: true } },
|
|
554
|
+
},
|
|
353
555
|
},
|
|
354
556
|
},
|
|
355
557
|
}
|
|
@@ -374,12 +576,14 @@ import { force } from 'prisma-guard'
|
|
|
374
576
|
const config = {
|
|
375
577
|
create: {
|
|
376
578
|
shape: {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
579
|
+
default: {
|
|
580
|
+
data: {
|
|
581
|
+
email: true, // client-controlled, @zod chains apply
|
|
582
|
+
name: true, // client-controlled
|
|
583
|
+
role: 'member', // forced to 'member', client cannot override
|
|
584
|
+
isActive: force(true), // forced to boolean true (force() needed to distinguish from client-controlled)
|
|
585
|
+
bio: (base) => base.max(500), // client-controlled with inline validation override
|
|
586
|
+
},
|
|
383
587
|
},
|
|
384
588
|
},
|
|
385
589
|
},
|
|
@@ -447,7 +651,7 @@ If the caller is missing or doesn't match any key, the request is rejected with
|
|
|
447
651
|
|
|
448
652
|
### Custom caller resolution
|
|
449
653
|
|
|
450
|
-
Use `resolveVariant` for caller logic beyond a simple header
|
|
654
|
+
Use `resolveVariant` for caller logic beyond a simple header. The callback parameter type depends on the target.
|
|
451
655
|
|
|
452
656
|
```ts
|
|
453
657
|
// Express
|
|
@@ -485,6 +689,27 @@ const userConfig = {
|
|
|
485
689
|
}
|
|
486
690
|
```
|
|
487
691
|
|
|
692
|
+
```ts
|
|
693
|
+
// Hono
|
|
694
|
+
const userConfig = {
|
|
695
|
+
findMany: {
|
|
696
|
+
shape: {
|
|
697
|
+
admin: { /* ... */ },
|
|
698
|
+
public: { /* ... */ },
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
guard: {
|
|
702
|
+
resolveVariant: (c) => {
|
|
703
|
+
const user = c.get('user')
|
|
704
|
+
if (user?.role === 'admin') return 'admin'
|
|
705
|
+
return 'public'
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
When using `c.get('user')` or other custom context values in TypeScript, add them to the `Variables` type of your Hono app so the call is typed correctly. For example: `Hono<{ Variables: { prisma: PrismaClient; user?: { role: string } } }>`.
|
|
712
|
+
|
|
488
713
|
`resolveVariant` takes priority over the header. If both are configured, the header is checked only when `resolveVariant` returns `undefined`.
|
|
489
714
|
|
|
490
715
|
### Parameterized caller patterns
|
|
@@ -539,13 +764,15 @@ import { force } from 'prisma-guard'
|
|
|
539
764
|
const projectConfig = {
|
|
540
765
|
findMany: {
|
|
541
766
|
shape: {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
767
|
+
default: {
|
|
768
|
+
where: {
|
|
769
|
+
status: { equals: 'published' }, // always filter to published
|
|
770
|
+
isDeleted: { equals: false }, // always exclude deleted
|
|
771
|
+
isActive: { equals: force(true) }, // force() needed for boolean true
|
|
772
|
+
title: { contains: true }, // client-controlled
|
|
773
|
+
},
|
|
774
|
+
take: { max: 50 },
|
|
547
775
|
},
|
|
548
|
-
take: { max: 50 },
|
|
549
776
|
},
|
|
550
777
|
},
|
|
551
778
|
}
|
|
@@ -570,14 +797,16 @@ Where shapes support `AND`, `OR`, and `NOT`. The combinator value defines which
|
|
|
570
797
|
const config = {
|
|
571
798
|
findMany: {
|
|
572
799
|
shape: {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
800
|
+
default: {
|
|
801
|
+
where: {
|
|
802
|
+
OR: {
|
|
803
|
+
title: { contains: true },
|
|
804
|
+
description: { contains: true },
|
|
805
|
+
},
|
|
806
|
+
status: { equals: 'published' }, // forced, always applied
|
|
577
807
|
},
|
|
578
|
-
|
|
808
|
+
take: { max: 50 },
|
|
579
809
|
},
|
|
580
|
-
take: { max: 50 },
|
|
581
810
|
},
|
|
582
811
|
},
|
|
583
812
|
}
|
|
@@ -606,15 +835,17 @@ Where shapes support relation-level filters. To-many relations use `some`, `ever
|
|
|
606
835
|
const userConfig = {
|
|
607
836
|
findMany: {
|
|
608
837
|
shape: {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
838
|
+
default: {
|
|
839
|
+
where: {
|
|
840
|
+
posts: {
|
|
841
|
+
some: {
|
|
842
|
+
title: { contains: true },
|
|
843
|
+
published: { equals: true }, // forced inside the relation
|
|
844
|
+
},
|
|
614
845
|
},
|
|
615
846
|
},
|
|
847
|
+
take: { max: 50 },
|
|
616
848
|
},
|
|
617
|
-
take: { max: 50 },
|
|
618
849
|
},
|
|
619
850
|
},
|
|
620
851
|
}
|
|
@@ -630,19 +861,21 @@ Shapes can restrict which response fields and relations the client may request:
|
|
|
630
861
|
const userConfig = {
|
|
631
862
|
findMany: {
|
|
632
863
|
shape: {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
864
|
+
default: {
|
|
865
|
+
where: { role: { equals: true } },
|
|
866
|
+
select: {
|
|
867
|
+
id: true,
|
|
868
|
+
email: true,
|
|
869
|
+
name: true,
|
|
870
|
+
posts: {
|
|
871
|
+
select: { id: true, title: true },
|
|
872
|
+
},
|
|
873
|
+
_count: {
|
|
874
|
+
select: { posts: true },
|
|
875
|
+
},
|
|
643
876
|
},
|
|
877
|
+
take: { max: 50 },
|
|
644
878
|
},
|
|
645
|
-
take: { max: 50 },
|
|
646
879
|
},
|
|
647
880
|
},
|
|
648
881
|
}
|
|
@@ -666,23 +899,25 @@ import { force } from 'prisma-guard'
|
|
|
666
899
|
const userConfig = {
|
|
667
900
|
findMany: {
|
|
668
901
|
shape: {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
902
|
+
default: {
|
|
903
|
+
include: {
|
|
904
|
+
posts: {
|
|
905
|
+
where: { isDeleted: { equals: false } }, // forced: never return deleted posts
|
|
906
|
+
orderBy: { createdAt: true },
|
|
907
|
+
take: { max: 20, default: 10 },
|
|
908
|
+
skip: true,
|
|
909
|
+
},
|
|
910
|
+
profile: true, // simple include, no constraints
|
|
911
|
+
_count: {
|
|
912
|
+
select: {
|
|
913
|
+
posts: {
|
|
914
|
+
where: { isDeleted: { equals: false } }, // count only non-deleted
|
|
915
|
+
},
|
|
681
916
|
},
|
|
682
917
|
},
|
|
683
918
|
},
|
|
919
|
+
take: { max: 50 },
|
|
684
920
|
},
|
|
685
|
-
take: { max: 50 },
|
|
686
921
|
},
|
|
687
922
|
},
|
|
688
923
|
}
|
|
@@ -696,20 +931,24 @@ Write operations that return records (`create`, `update`, `upsert`, `delete`, `c
|
|
|
696
931
|
const userConfig = {
|
|
697
932
|
create: {
|
|
698
933
|
shape: {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
934
|
+
default: {
|
|
935
|
+
data: { email: true, name: true },
|
|
936
|
+
include: {
|
|
937
|
+
profile: true,
|
|
938
|
+
},
|
|
702
939
|
},
|
|
703
940
|
},
|
|
704
941
|
},
|
|
705
942
|
update: {
|
|
706
943
|
shape: {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
944
|
+
default: {
|
|
945
|
+
data: { name: true },
|
|
946
|
+
where: { id: { equals: true } },
|
|
947
|
+
select: {
|
|
948
|
+
id: true,
|
|
949
|
+
name: true,
|
|
950
|
+
updatedAt: true,
|
|
951
|
+
},
|
|
713
952
|
},
|
|
714
953
|
},
|
|
715
954
|
},
|
|
@@ -730,16 +969,18 @@ import { force } from 'prisma-guard'
|
|
|
730
969
|
const projectConfig = {
|
|
731
970
|
upsert: {
|
|
732
971
|
shape: {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
972
|
+
default: {
|
|
973
|
+
where: { id: { equals: true } },
|
|
974
|
+
create: {
|
|
975
|
+
title: true,
|
|
976
|
+
status: 'draft',
|
|
977
|
+
isActive: force(true),
|
|
978
|
+
},
|
|
979
|
+
update: {
|
|
980
|
+
title: true,
|
|
981
|
+
},
|
|
982
|
+
select: { id: true, title: true, status: true },
|
|
741
983
|
},
|
|
742
|
-
select: { id: true, title: true, status: true },
|
|
743
984
|
},
|
|
744
985
|
},
|
|
745
986
|
}
|
|
@@ -755,13 +996,17 @@ All three (`where`, `create`, `update`) are required. Using `data` instead of `c
|
|
|
755
996
|
const userConfig = {
|
|
756
997
|
deleteMany: {
|
|
757
998
|
shape: {
|
|
758
|
-
|
|
999
|
+
default: {
|
|
1000
|
+
where: { isActive: { equals: true }, role: { equals: true } },
|
|
1001
|
+
},
|
|
759
1002
|
},
|
|
760
1003
|
},
|
|
761
1004
|
updateMany: {
|
|
762
1005
|
shape: {
|
|
763
|
-
|
|
764
|
-
|
|
1006
|
+
default: {
|
|
1007
|
+
data: { isActive: true },
|
|
1008
|
+
where: { role: { equals: true } },
|
|
1009
|
+
},
|
|
765
1010
|
},
|
|
766
1011
|
},
|
|
767
1012
|
}
|
|
@@ -801,6 +1046,8 @@ const prisma = new PrismaClient().$extends(
|
|
|
801
1046
|
}))
|
|
802
1047
|
)
|
|
803
1048
|
|
|
1049
|
+
app.use(express.json())
|
|
1050
|
+
|
|
804
1051
|
app.use((req, res, next) => {
|
|
805
1052
|
const tenantId = req.headers['x-tenant-id'] as string
|
|
806
1053
|
store.run({ tenantId }, () => {
|
|
@@ -812,13 +1059,17 @@ app.use((req, res, next) => {
|
|
|
812
1059
|
app.use('/', ProjectRouter({
|
|
813
1060
|
findMany: {
|
|
814
1061
|
shape: {
|
|
815
|
-
|
|
816
|
-
|
|
1062
|
+
default: {
|
|
1063
|
+
where: { title: { contains: true } },
|
|
1064
|
+
take: { max: 50 },
|
|
1065
|
+
},
|
|
817
1066
|
},
|
|
818
1067
|
},
|
|
819
1068
|
create: {
|
|
820
1069
|
shape: {
|
|
821
|
-
|
|
1070
|
+
default: {
|
|
1071
|
+
data: { title: true },
|
|
1072
|
+
},
|
|
822
1073
|
},
|
|
823
1074
|
},
|
|
824
1075
|
}))
|
|
@@ -843,7 +1094,7 @@ For upsert: `where`, `create`, `update`, `select`, `include`
|
|
|
843
1094
|
|
|
844
1095
|
### Guard error handling
|
|
845
1096
|
|
|
846
|
-
Guard errors are mapped to HTTP status codes by the generated error
|
|
1097
|
+
Guard errors are mapped to HTTP status codes by the generated error handler:
|
|
847
1098
|
|
|
848
1099
|
| Error type | HTTP status | When |
|
|
849
1100
|
| ------------- | ----------- | ----------------------------------------------------------------- |
|
|
@@ -874,6 +1125,8 @@ const prisma = new PrismaClient().$extends(
|
|
|
874
1125
|
|
|
875
1126
|
const app = express()
|
|
876
1127
|
|
|
1128
|
+
app.use(express.json())
|
|
1129
|
+
|
|
877
1130
|
app.use((req, res, next) => {
|
|
878
1131
|
const tenantId = req.headers['x-tenant-id'] as string
|
|
879
1132
|
const role = req.headers['x-role'] as string || 'viewer'
|
|
@@ -973,6 +1226,8 @@ All write operations accept the full Prisma args object as the JSON request body
|
|
|
973
1226
|
|
|
974
1227
|
Write operations that return records (create, update, delete, upsert, createManyAndReturn, updateManyAndReturn) support `select`, `include`, and `omit` in the request body to control the response shape.
|
|
975
1228
|
|
|
1229
|
+
For Express, mount `express.json()` before the router so request bodies are parsed. For Hono, malformed JSON bodies are rejected with 400 (`{ "message": "Invalid JSON in request body" }`) before reaching the handler.
|
|
1230
|
+
|
|
976
1231
|
### Bulk operations
|
|
977
1232
|
|
|
978
1233
|
`createMany`, `createManyAndReturn`, `updateMany`, and `updateManyAndReturn` accept scalar-only data inputs. Nested relation writes are not supported in bulk operations.
|
|
@@ -1007,17 +1262,19 @@ POST read endpoints are enabled by default. Disable them with `disablePostReads:
|
|
|
1007
1262
|
|
|
1008
1263
|
Most read operations use the same path for both GET and POST. The only exception is `findMany`, which uses a `/read` suffix to avoid conflicting with `POST /` (create).
|
|
1009
1264
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
|
1013
|
-
|
|
|
1014
|
-
|
|
|
1015
|
-
|
|
|
1016
|
-
|
|
|
1017
|
-
|
|
|
1018
|
-
|
|
|
1019
|
-
|
|
|
1020
|
-
|
|
|
1265
|
+
`{modelname}` in the paths below is the lowercased model name. See [Path casing in generated endpoints](#path-casing-in-generated-endpoints).
|
|
1266
|
+
|
|
1267
|
+
| Operation | GET path | POST path |
|
|
1268
|
+
| ----------------- | ---------------------------- | ---------------------------- |
|
|
1269
|
+
| findMany | `/{modelname}/` | `/{modelname}/read` |
|
|
1270
|
+
| findFirst | `/{modelname}/first` | `/{modelname}/first` |
|
|
1271
|
+
| findFirstOrThrow | `/{modelname}/first/strict` | `/{modelname}/first/strict` |
|
|
1272
|
+
| findUnique | `/{modelname}/unique` | `/{modelname}/unique` |
|
|
1273
|
+
| findUniqueOrThrow | `/{modelname}/unique/strict` | `/{modelname}/unique/strict` |
|
|
1274
|
+
| findManyPaginated | `/{modelname}/paginated` | `/{modelname}/paginated` |
|
|
1275
|
+
| count | `/{modelname}/count` | `/{modelname}/count` |
|
|
1276
|
+
| aggregate | `/{modelname}/aggregate` | `/{modelname}/aggregate` |
|
|
1277
|
+
| groupBy | `/{modelname}/groupby` | `/{modelname}/groupby` |
|
|
1021
1278
|
|
|
1022
1279
|
### Usage
|
|
1023
1280
|
|
|
@@ -1117,7 +1374,9 @@ All errors are returned as JSON with a `message` field:
|
|
|
1117
1374
|
{ "message": "Unique constraint violation" }
|
|
1118
1375
|
```
|
|
1119
1376
|
|
|
1120
|
-
Each generated router installs
|
|
1377
|
+
Each generated router installs error handling (Express middleware, Fastify `setErrorHandler`, or Hono `app.onError`) that normalizes errors. Prisma error codes are mapped to appropriate HTTP status codes. Guard errors are mapped as follows: `ShapeError` and `CallerError` → 400, `PolicyError` → 403.
|
|
1378
|
+
|
|
1379
|
+
For the Hono target, thrown `HTTPException` instances are caught by `app.onError` and converted to `{ "message": err.message }` with the exception's status code. Custom response bodies attached to `HTTPException` are not preserved — see [HTTPException normalization](#httpexception-normalization).
|
|
1121
1380
|
|
|
1122
1381
|
| Status | Description |
|
|
1123
1382
|
| ------ | ------------------------------------------ |
|
|
@@ -1139,12 +1398,12 @@ All incoming JSON bodies and query parameters are sanitized to reject `__proto__
|
|
|
1139
1398
|
|
|
1140
1399
|
Each router automatically registers OpenAPI spec endpoints when not in production:
|
|
1141
1400
|
|
|
1142
|
-
| Endpoint
|
|
1143
|
-
|
|
|
1144
|
-
| `/{
|
|
1145
|
-
| `/{
|
|
1401
|
+
| Endpoint | Description |
|
|
1402
|
+
| ------------------------------- | --------------------- |
|
|
1403
|
+
| `/{modelname}/openapi.json` | OpenAPI 3.1 JSON spec |
|
|
1404
|
+
| `/{modelname}/openapi.yaml` | OpenAPI 3.1 YAML spec |
|
|
1146
1405
|
|
|
1147
|
-
Actual paths depend on `customUrlPrefix` and `addModelPrefix` configuration.
|
|
1406
|
+
Actual paths depend on `customUrlPrefix` and `addModelPrefix` configuration. `{modelname}` is the lowercased model name (see [Path casing](#path-casing-in-generated-endpoints)).
|
|
1148
1407
|
|
|
1149
1408
|
The OpenAPI spec includes POST read endpoints when they are enabled (default). Each POST read operation appears with its own `operationId` and request body schema documenting the native JSON argument types.
|
|
1150
1409
|
|
|
@@ -1170,6 +1429,8 @@ const postConfig = {
|
|
|
1170
1429
|
enableAll: true,
|
|
1171
1430
|
}
|
|
1172
1431
|
|
|
1432
|
+
app.use(express.json())
|
|
1433
|
+
|
|
1173
1434
|
app.use('/', UserRouter(userConfig))
|
|
1174
1435
|
app.use('/', PostRouter(postConfig))
|
|
1175
1436
|
|
|
@@ -1230,14 +1491,70 @@ fastify.get('/docs', generateCombinedDocs({
|
|
|
1230
1491
|
}))
|
|
1231
1492
|
```
|
|
1232
1493
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1494
|
+
#### Hono
|
|
1495
|
+
|
|
1496
|
+
```ts
|
|
1497
|
+
import { Hono } from 'hono'
|
|
1498
|
+
import { PrismaClient } from '@prisma/client'
|
|
1499
|
+
import {
|
|
1500
|
+
generateCombinedDocs,
|
|
1501
|
+
registerModelDocs,
|
|
1502
|
+
} from './generated/combinedDocs'
|
|
1503
|
+
import { UserRouter } from './generated/User/UserRouter'
|
|
1504
|
+
import { PostRouter } from './generated/Post/PostRouter'
|
|
1505
|
+
|
|
1506
|
+
type Env = {
|
|
1507
|
+
Variables: {
|
|
1508
|
+
prisma: PrismaClient
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const prisma = new PrismaClient()
|
|
1513
|
+
|
|
1514
|
+
const userConfig = {
|
|
1515
|
+
findMany: { before: [async (c, next) => { /* auth */ await next() }] },
|
|
1516
|
+
create: {},
|
|
1517
|
+
findUnique: {},
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
const postConfig = {
|
|
1521
|
+
enableAll: true,
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
const app = new Hono<Env>()
|
|
1525
|
+
|
|
1526
|
+
app.use('*', async (c, next) => {
|
|
1527
|
+
c.set('prisma', prisma)
|
|
1528
|
+
await next()
|
|
1529
|
+
})
|
|
1530
|
+
|
|
1531
|
+
app.route('/', UserRouter(userConfig))
|
|
1532
|
+
app.route('/', PostRouter(postConfig))
|
|
1533
|
+
|
|
1534
|
+
registerModelDocs(app, '/docs', {
|
|
1535
|
+
User: userConfig,
|
|
1536
|
+
Post: postConfig,
|
|
1537
|
+
})
|
|
1538
|
+
|
|
1539
|
+
app.get('/docs', generateCombinedDocs({
|
|
1540
|
+
title: 'My API',
|
|
1541
|
+
modelConfigs: {
|
|
1542
|
+
User: userConfig,
|
|
1543
|
+
Post: postConfig,
|
|
1544
|
+
},
|
|
1545
|
+
}))
|
|
1546
|
+
```
|
|
1547
|
+
|
|
1548
|
+
| Endpoint | Description |
|
|
1549
|
+
| --------------------------------- | ----------------------- |
|
|
1550
|
+
| `/docs` | Combined index page |
|
|
1551
|
+
| `/docs/{modelname}` | Contract view (default) |
|
|
1552
|
+
| `/docs/{modelname}?ui=scalar` | Scalar interactive UI |
|
|
1553
|
+
| `/docs/{modelname}?ui=json` | Raw JSON |
|
|
1554
|
+
| `/docs/{modelname}?ui=yaml` | Raw YAML |
|
|
1555
|
+
| `/docs/{modelname}?ui=playground` | Query playground |
|
|
1556
|
+
|
|
1557
|
+
The `?ui=playground` endpoint requires `prisma-query-builder-ui`. For Express and Fastify, the builder is auto-started in development. For Hono, the builder must be started manually in a separate process (see [Query Builder](#query-builder)).
|
|
1241
1558
|
|
|
1242
1559
|
Disable in production via `NODE_ENV=production` or `DISABLE_OPENAPI=true`. Override with `disableOpenApi: false` in config to force-enable.
|
|
1243
1560
|
|
|
@@ -1258,18 +1575,20 @@ When `specBasePath` is not set, `customUrlPrefix` is used for both runtime route
|
|
|
1258
1575
|
|
|
1259
1576
|
## prisma-sql integration
|
|
1260
1577
|
|
|
1261
|
-
When `prisma-sql` is installed, the generated handlers automatically attempt to use its `speedExtension` for optimized SQL execution. The extension activates only when a database connector is provided on the request
|
|
1578
|
+
When `prisma-sql` is installed, the generated handlers automatically attempt to use its `speedExtension` for optimized SQL execution. The extension activates only when a database connector is provided on the request context.
|
|
1262
1579
|
|
|
1263
|
-
Set
|
|
1580
|
+
Set the connector in your middleware to activate the extension:
|
|
1264
1581
|
|
|
1265
1582
|
```ts
|
|
1266
1583
|
import { PrismaClient } from '@prisma/client'
|
|
1267
1584
|
import postgres from 'postgres'
|
|
1585
|
+
import { Hono } from 'hono'
|
|
1268
1586
|
|
|
1269
1587
|
const prisma = new PrismaClient()
|
|
1270
1588
|
const sql = postgres(process.env.DATABASE_URL!)
|
|
1271
1589
|
|
|
1272
1590
|
// Express
|
|
1591
|
+
app.use(express.json())
|
|
1273
1592
|
app.use((req, res, next) => {
|
|
1274
1593
|
req.prisma = prisma
|
|
1275
1594
|
req.postgres = sql
|
|
@@ -1281,9 +1600,26 @@ fastify.addHook('onRequest', async (request) => {
|
|
|
1281
1600
|
request.prisma = prisma
|
|
1282
1601
|
request.postgres = sql
|
|
1283
1602
|
})
|
|
1284
|
-
```
|
|
1285
1603
|
|
|
1286
|
-
|
|
1604
|
+
// Hono
|
|
1605
|
+
type Env = {
|
|
1606
|
+
Variables: {
|
|
1607
|
+
prisma: PrismaClient
|
|
1608
|
+
postgres: ReturnType<typeof postgres>
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const app = new Hono<Env>()
|
|
1613
|
+
|
|
1614
|
+
app.use('*', async (c, next) => {
|
|
1615
|
+
c.set('prisma', prisma)
|
|
1616
|
+
c.set('postgres', sql)
|
|
1617
|
+
await next()
|
|
1618
|
+
})```
|
|
1619
|
+
|
|
1620
|
+
Without a connector on the request context, the handlers use the standard PrismaClient. Set `DEBUG=true` in the environment to enable prisma-sql debug logging.
|
|
1621
|
+
|
|
1622
|
+
For SQLite, use `c.set('sqlite', sqliteConnector)` (Hono) or the equivalent on Express/Fastify, and add `sqlite` to the `Variables` type.
|
|
1287
1623
|
|
|
1288
1624
|
## Query parameter parsing
|
|
1289
1625
|
|
|
@@ -1291,37 +1627,41 @@ GET query values are parsed server-side. Strings starting with `{`, `[`, or `"`
|
|
|
1291
1627
|
|
|
1292
1628
|
POST read endpoints bypass this parsing entirely — the JSON body is used as-is with native types.
|
|
1293
1629
|
|
|
1630
|
+
On the Hono target, duplicate query keys collapse to the last value (`?a=1&a=2` → `a=2`). `encodeQueryParams` does not emit duplicate keys, so this only matters for hand-built query strings.
|
|
1631
|
+
|
|
1294
1632
|
## Router schema
|
|
1295
1633
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
|
1299
|
-
|
|
|
1300
|
-
|
|
|
1301
|
-
|
|
|
1302
|
-
|
|
|
1303
|
-
|
|
|
1304
|
-
|
|
|
1305
|
-
|
|
|
1306
|
-
|
|
|
1307
|
-
|
|
|
1308
|
-
|
|
|
1309
|
-
|
|
|
1310
|
-
|
|
|
1311
|
-
|
|
|
1312
|
-
|
|
|
1313
|
-
|
|
|
1314
|
-
|
|
|
1315
|
-
|
|
|
1316
|
-
|
|
|
1317
|
-
|
|
|
1318
|
-
|
|
|
1319
|
-
|
|
|
1320
|
-
|
|
|
1321
|
-
|
|
|
1322
|
-
|
|
|
1323
|
-
|
|
|
1324
|
-
|
|
|
1634
|
+
`{modelname}` in the paths below is the lowercased model name. For a `User` model, `/{modelname}/first` becomes `/user/first`. For `BlogPost`, it becomes `/blogpost/first`. See [Path casing in generated endpoints](#path-casing-in-generated-endpoints).
|
|
1635
|
+
|
|
1636
|
+
| Operation | Method | Path | Notes |
|
|
1637
|
+
| ------------------- | ------ | ---------------------------- | ---------------------------------- |
|
|
1638
|
+
| findMany | GET | `/{modelname}/` | |
|
|
1639
|
+
| findMany | POST | `/{modelname}/read` | POST read alternative |
|
|
1640
|
+
| findFirst | GET | `/{modelname}/first` | |
|
|
1641
|
+
| findFirst | POST | `/{modelname}/first` | POST read alternative |
|
|
1642
|
+
| findFirstOrThrow | GET | `/{modelname}/first/strict` | |
|
|
1643
|
+
| findFirstOrThrow | POST | `/{modelname}/first/strict` | POST read alternative |
|
|
1644
|
+
| findUnique | GET | `/{modelname}/unique` | |
|
|
1645
|
+
| findUnique | POST | `/{modelname}/unique` | POST read alternative |
|
|
1646
|
+
| findUniqueOrThrow | GET | `/{modelname}/unique/strict` | |
|
|
1647
|
+
| findUniqueOrThrow | POST | `/{modelname}/unique/strict` | POST read alternative |
|
|
1648
|
+
| findManyPaginated | GET | `/{modelname}/paginated` | |
|
|
1649
|
+
| findManyPaginated | POST | `/{modelname}/paginated` | POST read alternative |
|
|
1650
|
+
| count | GET | `/{modelname}/count` | |
|
|
1651
|
+
| count | POST | `/{modelname}/count` | POST read alternative |
|
|
1652
|
+
| aggregate | GET | `/{modelname}/aggregate` | |
|
|
1653
|
+
| aggregate | POST | `/{modelname}/aggregate` | POST read alternative |
|
|
1654
|
+
| groupBy | GET | `/{modelname}/groupby` | |
|
|
1655
|
+
| groupBy | POST | `/{modelname}/groupby` | POST read alternative |
|
|
1656
|
+
| create | POST | `/{modelname}/` | |
|
|
1657
|
+
| createMany | POST | `/{modelname}/many` | |
|
|
1658
|
+
| createManyAndReturn | POST | `/{modelname}/many/return` | |
|
|
1659
|
+
| update | PUT | `/{modelname}/` | |
|
|
1660
|
+
| updateMany | PUT | `/{modelname}/many` | |
|
|
1661
|
+
| updateManyAndReturn | PUT | `/{modelname}/many/return` | |
|
|
1662
|
+
| upsert | PATCH | `/{modelname}/` | |
|
|
1663
|
+
| delete | DELETE | `/{modelname}/` | |
|
|
1664
|
+
| deleteMany | DELETE | `/{modelname}/many` | |
|
|
1325
1665
|
|
|
1326
1666
|
Paths shown are relative suffixes. Actual paths include the model prefix (e.g., `/user/first`) unless `addModelPrefix: false`, and any `customUrlPrefix`.
|
|
1327
1667
|
|
|
@@ -1427,6 +1767,27 @@ type FastifyHookHandler = (
|
|
|
1427
1767
|
|
|
1428
1768
|
The `guard.resolveVariant` callback receives `FastifyRequest` instead of `Request`.
|
|
1429
1769
|
|
|
1770
|
+
### Hono
|
|
1771
|
+
|
|
1772
|
+
The Hono config is identical except for hook and resolver types:
|
|
1773
|
+
|
|
1774
|
+
```ts
|
|
1775
|
+
interface OperationConfig {
|
|
1776
|
+
before?: HonoHookHandler[]
|
|
1777
|
+
after?: HonoHookHandler[]
|
|
1778
|
+
shape?: Record<string, any>
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
type HonoHookHandler<Env extends { Variables: any } = any> = (
|
|
1782
|
+
c: Context<Env>,
|
|
1783
|
+
next: Next,
|
|
1784
|
+
) => Promise<Response | void> | Response | void
|
|
1785
|
+
```
|
|
1786
|
+
|
|
1787
|
+
The `guard.resolveVariant` callback receives Hono's `Context`. Hooks are native Hono middleware — call `await next()` to continue the chain, return a `Response` (or throw `HTTPException`) to short-circuit.
|
|
1788
|
+
|
|
1789
|
+
The Hono router does not auto-start the Query Builder. Set `queryBuilder: false` to make the playground route return 404, or run `prisma-query-builder-ui` manually for development.
|
|
1790
|
+
|
|
1430
1791
|
### Shared options
|
|
1431
1792
|
|
|
1432
1793
|
`customUrlPrefix` is normalized to ensure a leading slash and strip trailing slashes.
|