prisma-generator-express 1.39.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 CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Coverage](https://img.shields.io/codecov/c/github/multipliedtwice/prisma-generator-express/main.svg)](https://codecov.io/gh/multipliedtwice/prisma-generator-express)
6
6
  [![npm](https://img.shields.io/npm/l/prisma-generator-express.svg)](LICENSE)
7
7
 
8
- Prisma generator that creates Express or Fastify CRUD API routes with OpenAPI documentation from your Prisma schema.
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 both **Express** and **Fastify** targets via the `target` configuration option.
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 generate Fastify routes instead of Express, set the `target` config:
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
- Valid `target` values are `"express"` (default) and `"fastify"`.
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
- ### Key differences between Express and Fastify targets
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
199
319
 
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 | `before`/`after` are Express `RequestHandler[]` | `before`/`after` are `FastifyHookHandler[]` |
205
- | Guard resolveVariant | Receives `express.Request` | Receives `FastifyRequest` |
206
- | Request data | `req.prisma`, `res.locals` | `request.prisma`, `request` properties |
207
- | Error handling | Express error middleware on the router | Fastify `setErrorHandler` on the instance |
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
361
+
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 for both Express and Fastify targets. The only difference is the type of the `resolveVariant` callback parameter (`Request` vs `FastifyRequest`).
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 express routes and the guard artifacts.
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()
@@ -312,16 +496,20 @@ app.use('/', UserRouter({
312
496
  app.listen(3000)
313
497
  ```
314
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
+
315
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().`
316
502
 
317
503
  ### How guard integration works
318
504
 
319
505
  Each operation config accepts an optional `shape` property. When present, the generated handler:
320
506
 
321
- 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)`)
322
508
  2. Resolves the caller from `config.guard.resolveVariant(req)`, then from the configured header (default `x-api-variant`), falling back to `undefined`
323
509
  3. Calls `prisma.model.guard(shape, caller).method(args)` instead of `prisma.model.method(args)`
324
510
 
511
+ The downstream handler reads these values (`res.locals.guardShape`, `request.guardShape`, `c.get('guardShape')`) when constructing the Prisma call.
512
+
325
513
  When `shape` is absent, the handler calls Prisma directly with no guard enforcement.
326
514
 
327
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.
@@ -463,7 +651,7 @@ If the caller is missing or doesn't match any key, the request is rejected with
463
651
 
464
652
  ### Custom caller resolution
465
653
 
466
- 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.
467
655
 
468
656
  ```ts
469
657
  // Express
@@ -501,6 +689,27 @@ const userConfig = {
501
689
  }
502
690
  ```
503
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
+
504
713
  `resolveVariant` takes priority over the header. If both are configured, the header is checked only when `resolveVariant` returns `undefined`.
505
714
 
506
715
  ### Parameterized caller patterns
@@ -837,6 +1046,8 @@ const prisma = new PrismaClient().$extends(
837
1046
  }))
838
1047
  )
839
1048
 
1049
+ app.use(express.json())
1050
+
840
1051
  app.use((req, res, next) => {
841
1052
  const tenantId = req.headers['x-tenant-id'] as string
842
1053
  store.run({ tenantId }, () => {
@@ -883,7 +1094,7 @@ For upsert: `where`, `create`, `update`, `select`, `include`
883
1094
 
884
1095
  ### Guard error handling
885
1096
 
886
- Guard errors are mapped to HTTP status codes by the generated error-handling middleware:
1097
+ Guard errors are mapped to HTTP status codes by the generated error handler:
887
1098
 
888
1099
  | Error type | HTTP status | When |
889
1100
  | ------------- | ----------- | ----------------------------------------------------------------- |
@@ -914,6 +1125,8 @@ const prisma = new PrismaClient().$extends(
914
1125
 
915
1126
  const app = express()
916
1127
 
1128
+ app.use(express.json())
1129
+
917
1130
  app.use((req, res, next) => {
918
1131
  const tenantId = req.headers['x-tenant-id'] as string
919
1132
  const role = req.headers['x-role'] as string || 'viewer'
@@ -1013,6 +1226,8 @@ All write operations accept the full Prisma args object as the JSON request body
1013
1226
 
1014
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.
1015
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
+
1016
1231
  ### Bulk operations
1017
1232
 
1018
1233
  `createMany`, `createManyAndReturn`, `updateMany`, and `updateManyAndReturn` accept scalar-only data inputs. Nested relation writes are not supported in bulk operations.
@@ -1047,17 +1262,19 @@ POST read endpoints are enabled by default. Disable them with `disablePostReads:
1047
1262
 
1048
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).
1049
1264
 
1050
- | Operation | GET path | POST path |
1051
- | ----------------- | ---------------- | ---------------- |
1052
- | findMany | `/{modelName}/` | `/{modelName}/read` |
1053
- | findFirst | `/{modelName}/first` | `/{modelName}/first` |
1054
- | findFirstOrThrow | `/{modelName}/first/strict` | `/{modelName}/first/strict` |
1055
- | findUnique | `/{modelName}/unique` | `/{modelName}/unique` |
1056
- | findUniqueOrThrow | `/{modelName}/unique/strict` | `/{modelName}/unique/strict` |
1057
- | findManyPaginated | `/{modelName}/paginated` | `/{modelName}/paginated` |
1058
- | count | `/{modelName}/count` | `/{modelName}/count` |
1059
- | aggregate | `/{modelName}/aggregate` | `/{modelName}/aggregate` |
1060
- | groupBy | `/{modelName}/groupby` | `/{modelName}/groupby` |
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` |
1061
1278
 
1062
1279
  ### Usage
1063
1280
 
@@ -1157,7 +1374,9 @@ All errors are returned as JSON with a `message` field:
1157
1374
  { "message": "Unique constraint violation" }
1158
1375
  ```
1159
1376
 
1160
- Each generated router installs an error-handling middleware (Express) or error handler (Fastify) 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.
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).
1161
1380
 
1162
1381
  | Status | Description |
1163
1382
  | ------ | ------------------------------------------ |
@@ -1179,12 +1398,12 @@ All incoming JSON bodies and query parameters are sanitized to reject `__proto__
1179
1398
 
1180
1399
  Each router automatically registers OpenAPI spec endpoints when not in production:
1181
1400
 
1182
- | Endpoint | Description |
1183
- | ----------------------- | --------------------- |
1184
- | `/{modelName}/openapi.json` | OpenAPI 3.1 JSON spec |
1185
- | `/{modelName}/openapi.yaml` | OpenAPI 3.1 YAML spec |
1401
+ | Endpoint | Description |
1402
+ | ------------------------------- | --------------------- |
1403
+ | `/{modelname}/openapi.json` | OpenAPI 3.1 JSON spec |
1404
+ | `/{modelname}/openapi.yaml` | OpenAPI 3.1 YAML spec |
1186
1405
 
1187
- 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)).
1188
1407
 
1189
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.
1190
1409
 
@@ -1210,6 +1429,8 @@ const postConfig = {
1210
1429
  enableAll: true,
1211
1430
  }
1212
1431
 
1432
+ app.use(express.json())
1433
+
1213
1434
  app.use('/', UserRouter(userConfig))
1214
1435
  app.use('/', PostRouter(postConfig))
1215
1436
 
@@ -1270,14 +1491,70 @@ fastify.get('/docs', generateCombinedDocs({
1270
1491
  }))
1271
1492
  ```
1272
1493
 
1273
- | Endpoint | Description |
1274
- | ----------------------------- | ----------------------- |
1275
- | `/docs` | Combined index page |
1276
- | `/docs/{modelName}` | Contract view (default) |
1277
- | `/docs/{modelName}?ui=scalar` | Scalar interactive UI |
1278
- | `/docs/{modelName}?ui=json` | Raw JSON |
1279
- | `/docs/{modelName}?ui=yaml` | Raw YAML |
1280
- | `/docs/{modelName}?ui=playground` | Query playground |
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)).
1281
1558
 
1282
1559
  Disable in production via `NODE_ENV=production` or `DISABLE_OPENAPI=true`. Override with `disableOpenApi: false` in config to force-enable.
1283
1560
 
@@ -1298,18 +1575,20 @@ When `specBasePath` is not set, `customUrlPrefix` is used for both runtime route
1298
1575
 
1299
1576
  ## prisma-sql integration
1300
1577
 
1301
- 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 object.
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.
1302
1579
 
1303
- Set `req.postgres` or `req.sqlite` (Express) / `request.postgres` or `request.sqlite` (Fastify) in your middleware to activate the extension:
1580
+ Set the connector in your middleware to activate the extension:
1304
1581
 
1305
1582
  ```ts
1306
1583
  import { PrismaClient } from '@prisma/client'
1307
1584
  import postgres from 'postgres'
1585
+ import { Hono } from 'hono'
1308
1586
 
1309
1587
  const prisma = new PrismaClient()
1310
1588
  const sql = postgres(process.env.DATABASE_URL!)
1311
1589
 
1312
1590
  // Express
1591
+ app.use(express.json())
1313
1592
  app.use((req, res, next) => {
1314
1593
  req.prisma = prisma
1315
1594
  req.postgres = sql
@@ -1321,9 +1600,26 @@ fastify.addHook('onRequest', async (request) => {
1321
1600
  request.prisma = prisma
1322
1601
  request.postgres = sql
1323
1602
  })
1324
- ```
1325
1603
 
1326
- Without a connector on the request, the handlers use the standard PrismaClient. Set `DEBUG=true` in the environment to enable prisma-sql debug logging.
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.
1327
1623
 
1328
1624
  ## Query parameter parsing
1329
1625
 
@@ -1331,37 +1627,41 @@ GET query values are parsed server-side. Strings starting with `{`, `[`, or `"`
1331
1627
 
1332
1628
  POST read endpoints bypass this parsing entirely — the JSON body is used as-is with native types.
1333
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
+
1334
1632
  ## Router schema
1335
1633
 
1336
- | Operation | Method | Path | Notes |
1337
- | ------------------- | ------ | ---------------- | ---------------------------------- |
1338
- | findMany | GET | `/{modelName}/` | |
1339
- | findMany | POST | `/{modelName}/read` | POST read alternative |
1340
- | findFirst | GET | `/{modelName}/first` | |
1341
- | findFirst | POST | `/{modelName}/first` | POST read alternative |
1342
- | findFirstOrThrow | GET | `/{modelName}/first/strict` | |
1343
- | findFirstOrThrow | POST | `/{modelName}/first/strict` | POST read alternative |
1344
- | findUnique | GET | `/{modelName}/unique` | |
1345
- | findUnique | POST | `/{modelName}/unique` | POST read alternative |
1346
- | findUniqueOrThrow | GET | `/{modelName}/unique/strict` | |
1347
- | findUniqueOrThrow | POST | `/{modelName}/unique/strict` | POST read alternative |
1348
- | findManyPaginated | GET | `/{modelName}/paginated` | |
1349
- | findManyPaginated | POST | `/{modelName}/paginated` | POST read alternative |
1350
- | count | GET | `/{modelName}/count` | |
1351
- | count | POST | `/{modelName}/count` | POST read alternative |
1352
- | aggregate | GET | `/{modelName}/aggregate` | |
1353
- | aggregate | POST | `/{modelName}/aggregate` | POST read alternative |
1354
- | groupBy | GET | `/{modelName}/groupby` | |
1355
- | groupBy | POST | `/{modelName}/groupby` | POST read alternative |
1356
- | create | POST | `/{modelName}/` | |
1357
- | createMany | POST | `/{modelName}/many` | |
1358
- | createManyAndReturn | POST | `/{modelName}/many/return` | |
1359
- | update | PUT | `/{modelName}/` | |
1360
- | updateMany | PUT | `/{modelName}/many` | |
1361
- | updateManyAndReturn | PUT | `/{modelName}/many/return` | |
1362
- | upsert | PATCH | `/{modelName}/` | |
1363
- | delete | DELETE | `/{modelName}/` | |
1364
- | deleteMany | DELETE | `/{modelName}/many` | |
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` | |
1365
1665
 
1366
1666
  Paths shown are relative suffixes. Actual paths include the model prefix (e.g., `/user/first`) unless `addModelPrefix: false`, and any `customUrlPrefix`.
1367
1667
 
@@ -1467,6 +1767,27 @@ type FastifyHookHandler = (
1467
1767
 
1468
1768
  The `guard.resolveVariant` callback receives `FastifyRequest` instead of `Request`.
1469
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
+
1470
1791
  ### Shared options
1471
1792
 
1472
1793
  `customUrlPrefix` is normalized to ensure a leading slash and strip trailing slashes.