hono-crud 0.4.1 → 0.4.3
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/CHANGELOG.md +4 -0
- package/README.md +268 -116
- package/docs/advanced-features.md +881 -0
- package/docs/alternative-api-patterns.md +531 -0
- package/docs/authentication.md +285 -0
- package/docs/caching.md +175 -0
- package/docs/database-adapters.md +409 -0
- package/docs/logging.md +211 -0
- package/docs/rate-limiting.md +183 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
%b
|
|
16
16
|
%b
|
|
17
17
|
%b
|
|
18
|
+
%b
|
|
19
|
+
%b
|
|
18
20
|
## [0.1.0] - 2025-01-29
|
|
19
21
|
|
|
20
22
|
### Added
|
|
@@ -46,3 +48,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
46
48
|
[0.3.2]: https://github.com/kshdotdev/hono-crud/compare/v0.3.1...v0.3.2
|
|
47
49
|
[0.4.0]: https://github.com/kshdotdev/hono-crud/compare/v0.3.2...v0.4.0
|
|
48
50
|
[0.4.1]: https://github.com/kshdotdev/hono-crud/compare/v0.4.0...v0.4.1
|
|
51
|
+
[0.4.2]: https://github.com/kshdotdev/hono-crud/compare/v0.4.1...v0.4.2
|
|
52
|
+
[0.4.3]: https://github.com/kshdotdev/hono-crud/compare/v0.4.2...v0.4.3
|
package/README.md
CHANGED
|
@@ -4,196 +4,348 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Type-safe CRUD generator for [Hono](https://hono.dev) with Zod validation and automatic OpenAPI documentation.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **Full CRUD Operations** -
|
|
12
|
-
- **OpenAPI/Swagger** - Auto-generated
|
|
13
|
-
- **Database Adapters** -
|
|
11
|
+
- **Full CRUD Operations** - Generate Create, Read, Update, Delete, List endpoints with one call
|
|
12
|
+
- **OpenAPI/Swagger** - Auto-generated docs with Swagger UI, Scalar, and ReDoc
|
|
13
|
+
- **Database Adapters** - Memory (prototyping), Drizzle ORM, and Prisma
|
|
14
|
+
- **4 API Patterns** - Class-based, functional, builder, and config-based
|
|
14
15
|
- **Zod Validation** - Type-safe request/response validation
|
|
15
16
|
- **TypeScript First** - Full type inference and autocompletion
|
|
16
17
|
- **Edge Ready** - Works with Cloudflare Workers, Deno, Bun, and Node.js
|
|
17
|
-
- **
|
|
18
|
+
- **Authentication** - JWT, API Key middleware with role/permission guards
|
|
19
|
+
- **Caching** - Response caching with automatic invalidation
|
|
20
|
+
- **Rate Limiting** - Fixed/sliding window with tier-based limits
|
|
21
|
+
- **Advanced Features** - Soft delete, relations, batch operations, search, versioning, audit logging, and more
|
|
18
22
|
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
21
25
|
```bash
|
|
22
|
-
|
|
23
|
-
npm install hono-crud
|
|
24
|
-
|
|
25
|
-
# pnpm
|
|
26
|
-
pnpm add hono-crud
|
|
27
|
-
|
|
28
|
-
# yarn
|
|
29
|
-
yarn add hono-crud
|
|
30
|
-
|
|
31
|
-
# bun
|
|
32
|
-
bun add hono-crud
|
|
26
|
+
npm install hono-crud hono zod
|
|
33
27
|
```
|
|
34
28
|
|
|
29
|
+
Peer dependencies: `hono >= 4.0.0` and `zod >= 4.0.0` are required.
|
|
30
|
+
|
|
35
31
|
## Quick Start
|
|
36
32
|
|
|
37
33
|
```typescript
|
|
38
|
-
import { Hono } from
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
import { Hono } from 'hono';
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
import { fromHono, registerCrud, setupSwaggerUI, defineModel, defineMeta } from 'hono-crud';
|
|
37
|
+
import {
|
|
38
|
+
MemoryCreateEndpoint,
|
|
39
|
+
MemoryReadEndpoint,
|
|
40
|
+
MemoryUpdateEndpoint,
|
|
41
|
+
MemoryDeleteEndpoint,
|
|
42
|
+
MemoryListEndpoint,
|
|
43
|
+
} from 'hono-crud/adapters/memory';
|
|
44
|
+
|
|
45
|
+
// 1. Define your schema
|
|
46
46
|
const UserSchema = z.object({
|
|
47
|
-
id: z.
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
id: z.uuid(),
|
|
48
|
+
email: z.email(),
|
|
49
|
+
name: z.string().min(1),
|
|
50
|
+
role: z.enum(['admin', 'user']),
|
|
50
51
|
});
|
|
51
52
|
|
|
52
|
-
// Create
|
|
53
|
-
const
|
|
54
|
-
|
|
53
|
+
// 2. Create model + meta
|
|
54
|
+
const UserModel = defineModel({
|
|
55
|
+
tableName: 'users',
|
|
56
|
+
schema: UserSchema,
|
|
57
|
+
primaryKeys: ['id'],
|
|
55
58
|
});
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const userMeta = defineMeta({ model: UserModel });
|
|
61
|
+
|
|
62
|
+
// 3. Define endpoints
|
|
63
|
+
class UserCreate extends MemoryCreateEndpoint {
|
|
64
|
+
_meta = userMeta;
|
|
65
|
+
schema = { tags: ['Users'], summary: 'Create a user' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class UserList extends MemoryListEndpoint {
|
|
69
|
+
_meta = userMeta;
|
|
70
|
+
schema = { tags: ['Users'], summary: 'List users' };
|
|
71
|
+
filterFields = ['role'];
|
|
72
|
+
searchFields = ['name', 'email'];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class UserRead extends MemoryReadEndpoint {
|
|
76
|
+
_meta = userMeta;
|
|
77
|
+
schema = { tags: ['Users'], summary: 'Get a user' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class UserUpdate extends MemoryUpdateEndpoint {
|
|
81
|
+
_meta = userMeta;
|
|
82
|
+
schema = { tags: ['Users'], summary: 'Update a user' };
|
|
83
|
+
allowedUpdateFields = ['name', 'role'];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class UserDelete extends MemoryDeleteEndpoint {
|
|
87
|
+
_meta = userMeta;
|
|
88
|
+
schema = { tags: ['Users'], summary: 'Delete a user' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 4. Wire it up
|
|
92
|
+
const app = fromHono(new Hono());
|
|
93
|
+
|
|
94
|
+
registerCrud(app, '/users', {
|
|
95
|
+
create: UserCreate,
|
|
96
|
+
list: UserList,
|
|
97
|
+
read: UserRead,
|
|
98
|
+
update: UserUpdate,
|
|
99
|
+
delete: UserDelete,
|
|
60
100
|
});
|
|
61
101
|
|
|
62
|
-
//
|
|
63
|
-
app.
|
|
102
|
+
// 5. OpenAPI docs
|
|
103
|
+
app.doc('/openapi.json', {
|
|
104
|
+
openapi: '3.1.0',
|
|
105
|
+
info: { title: 'My API', version: '1.0.0' },
|
|
106
|
+
});
|
|
107
|
+
setupSwaggerUI(app, { docsPath: '/docs', specPath: '/openapi.json' });
|
|
64
108
|
|
|
65
109
|
export default app;
|
|
66
110
|
```
|
|
67
111
|
|
|
68
|
-
|
|
112
|
+
This generates:
|
|
69
113
|
|
|
70
|
-
|
|
114
|
+
| Method | Route | Description |
|
|
115
|
+
|--------|-------|-------------|
|
|
116
|
+
| `POST` | `/users` | Create a user |
|
|
117
|
+
| `GET` | `/users` | List users (with filtering, search, pagination) |
|
|
118
|
+
| `GET` | `/users/:id` | Get a user by ID |
|
|
119
|
+
| `PATCH` | `/users/:id` | Update a user |
|
|
120
|
+
| `DELETE` | `/users/:id` | Delete a user |
|
|
71
121
|
|
|
72
|
-
|
|
122
|
+
## API Patterns
|
|
73
123
|
|
|
74
|
-
|
|
75
|
-
|
|
124
|
+
hono-crud supports four ways to define endpoints. All produce classes compatible with `registerCrud()` and can be mixed.
|
|
125
|
+
|
|
126
|
+
| Pattern | Best For | Style |
|
|
127
|
+
|---------|----------|-------|
|
|
128
|
+
| **Class-based** | Complex logic, database adapters | `class UserList extends MemoryListEndpoint { ... }` |
|
|
129
|
+
| **Functional** | Quick setup | `createList({ meta, filterFields: ['role'] }, MemoryListEndpoint)` |
|
|
130
|
+
| **Builder** | Readable chains | `crud(meta).list().filter('role').build(MemoryListEndpoint)` |
|
|
131
|
+
| **Config-based** | Declarative, all-in-one | `defineEndpoints({ meta, list: { ... } }, MemoryAdapters)` |
|
|
76
132
|
|
|
77
|
-
|
|
133
|
+
```typescript
|
|
134
|
+
import { createList, crud, defineEndpoints, MemoryAdapters } from 'hono-crud';
|
|
135
|
+
|
|
136
|
+
// Functional
|
|
137
|
+
const UserList = createList(
|
|
138
|
+
{ meta: userMeta, filterFields: ['role'], searchFields: ['name'] },
|
|
139
|
+
MemoryListEndpoint
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Builder
|
|
143
|
+
const UserList = crud(userMeta)
|
|
144
|
+
.list()
|
|
145
|
+
.filter('role')
|
|
146
|
+
.search('name')
|
|
147
|
+
.pagination(20, 100)
|
|
148
|
+
.build(MemoryListEndpoint);
|
|
149
|
+
|
|
150
|
+
// Config-based (all endpoints at once)
|
|
151
|
+
const endpoints = defineEndpoints({
|
|
152
|
+
meta: userMeta,
|
|
153
|
+
create: { openapi: { tags: ['Users'], summary: 'Create user' } },
|
|
154
|
+
list: { filtering: { fields: ['role'] }, search: { fields: ['name'] } },
|
|
155
|
+
read: {},
|
|
156
|
+
update: { fields: { allowed: ['name', 'role'] } },
|
|
157
|
+
delete: {},
|
|
158
|
+
}, MemoryAdapters);
|
|
159
|
+
|
|
160
|
+
registerCrud(app, '/users', endpoints);
|
|
78
161
|
```
|
|
79
162
|
|
|
80
|
-
|
|
163
|
+
See [docs/alternative-api-patterns.md](./docs/alternative-api-patterns.md) for the full reference.
|
|
81
164
|
|
|
82
|
-
|
|
165
|
+
## Database Adapters
|
|
83
166
|
|
|
84
|
-
|
|
85
|
-
import { DrizzleAdapter } from "hono-crud/adapters/drizzle";
|
|
86
|
-
import { db } from "./db";
|
|
87
|
-
import { users } from "./schema";
|
|
167
|
+
### Memory
|
|
88
168
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
169
|
+
Zero-config, perfect for prototyping and tests:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { MemoryCreateEndpoint, MemoryListEndpoint /* ... */ } from 'hono-crud/adapters/memory';
|
|
92
173
|
```
|
|
93
174
|
|
|
94
|
-
###
|
|
175
|
+
### Drizzle
|
|
95
176
|
|
|
96
|
-
|
|
177
|
+
Use `createDrizzleCrud` for minimal boilerplate:
|
|
97
178
|
|
|
98
179
|
```typescript
|
|
99
|
-
import {
|
|
100
|
-
import {
|
|
180
|
+
import { createDrizzleCrud } from 'hono-crud/adapters/drizzle';
|
|
181
|
+
import { db } from './db';
|
|
182
|
+
|
|
183
|
+
const User = createDrizzleCrud(db, userMeta);
|
|
184
|
+
|
|
185
|
+
class UserCreate extends User.Create {
|
|
186
|
+
schema = { tags: ['Users'], summary: 'Create user' };
|
|
187
|
+
}
|
|
101
188
|
|
|
102
|
-
|
|
103
|
-
|
|
189
|
+
class UserList extends User.List {
|
|
190
|
+
schema = { tags: ['Users'], summary: 'List users' };
|
|
191
|
+
filterFields = ['role'];
|
|
192
|
+
}
|
|
104
193
|
```
|
|
105
194
|
|
|
106
|
-
|
|
195
|
+
Or set `db` directly on each endpoint class:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { DrizzleListEndpoint } from 'hono-crud/adapters/drizzle';
|
|
199
|
+
|
|
200
|
+
class UserList extends DrizzleListEndpoint {
|
|
201
|
+
_meta = userMeta;
|
|
202
|
+
db = drizzleDb;
|
|
203
|
+
filterFields = ['role'];
|
|
204
|
+
}
|
|
205
|
+
```
|
|
107
206
|
|
|
108
|
-
|
|
207
|
+
### Prisma
|
|
109
208
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
- **OpenAPI JSON**: `/openapi.json`
|
|
209
|
+
```typescript
|
|
210
|
+
import { PrismaListEndpoint } from 'hono-crud/adapters/prisma';
|
|
113
211
|
|
|
114
|
-
|
|
212
|
+
class UserList extends PrismaListEndpoint {
|
|
213
|
+
_meta = userMeta;
|
|
214
|
+
prisma = prismaClient;
|
|
215
|
+
filterFields = ['role'];
|
|
216
|
+
}
|
|
217
|
+
```
|
|
115
218
|
|
|
116
|
-
|
|
219
|
+
See [docs/database-adapters.md](./docs/database-adapters.md) for complete setup guides.
|
|
117
220
|
|
|
118
|
-
|
|
221
|
+
## Authentication
|
|
119
222
|
|
|
120
|
-
|
|
223
|
+
Built-in JWT and API Key middleware with composable guards:
|
|
121
224
|
|
|
122
225
|
```typescript
|
|
123
|
-
import {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
user: typeof auth.$Infer.Session.user | null;
|
|
130
|
-
session: typeof auth.$Infer.Session.session | null;
|
|
131
|
-
};
|
|
132
|
-
}>();
|
|
133
|
-
|
|
134
|
-
// CORS (must be before routes)
|
|
135
|
-
app.use("/api/auth/*", cors({
|
|
136
|
-
origin: "http://localhost:3000",
|
|
137
|
-
credentials: true,
|
|
226
|
+
import { createJWTMiddleware, requireRoles, requireAuthenticated, anyOf } from 'hono-crud';
|
|
227
|
+
|
|
228
|
+
// JWT middleware
|
|
229
|
+
app.use('/api/*', createJWTMiddleware({
|
|
230
|
+
secret: process.env.JWT_SECRET!,
|
|
231
|
+
issuer: 'my-app',
|
|
138
232
|
}));
|
|
139
233
|
|
|
140
|
-
//
|
|
141
|
-
app
|
|
234
|
+
// Guards on specific endpoints
|
|
235
|
+
registerCrud(app, '/users', endpoints, {
|
|
236
|
+
middlewares: [requireAuthenticated()],
|
|
237
|
+
endpointMiddlewares: {
|
|
238
|
+
delete: [requireRoles('admin')],
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Composable guards
|
|
243
|
+
app.use('/admin/*', anyOf(
|
|
244
|
+
requireRoles('admin'),
|
|
245
|
+
requireOwnership((ctx) => ctx.req.param('id'))
|
|
246
|
+
));
|
|
142
247
|
```
|
|
143
248
|
|
|
144
|
-
|
|
249
|
+
See [docs/authentication.md](./docs/authentication.md) for JWT, API Key, guards, and better-auth integration.
|
|
250
|
+
|
|
251
|
+
## Middleware
|
|
252
|
+
|
|
253
|
+
### Caching
|
|
145
254
|
|
|
146
255
|
```typescript
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
256
|
+
import { withCache, withCacheInvalidation, setCacheStorage, MemoryCacheStorage } from 'hono-crud';
|
|
257
|
+
|
|
258
|
+
class UserRead extends withCache(MemoryReadEndpoint) {
|
|
259
|
+
_meta = userMeta;
|
|
260
|
+
cacheConfig = { ttl: 300, perUser: false };
|
|
261
|
+
}
|
|
153
262
|
```
|
|
154
263
|
|
|
155
|
-
|
|
264
|
+
See [docs/caching.md](./docs/caching.md).
|
|
265
|
+
|
|
266
|
+
### Rate Limiting
|
|
156
267
|
|
|
157
268
|
```typescript
|
|
158
|
-
import {
|
|
269
|
+
import { createRateLimitMiddleware, setRateLimitStorage, MemoryRateLimitStorage } from 'hono-crud';
|
|
159
270
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
endpointMiddlewares: {
|
|
168
|
-
delete: [requireRoles(["admin"])],
|
|
169
|
-
},
|
|
170
|
-
});
|
|
271
|
+
setRateLimitStorage(new MemoryRateLimitStorage());
|
|
272
|
+
|
|
273
|
+
app.use('/api/*', createRateLimitMiddleware({
|
|
274
|
+
limit: 100,
|
|
275
|
+
windowSeconds: 60,
|
|
276
|
+
keyStrategy: 'ip',
|
|
277
|
+
}));
|
|
171
278
|
```
|
|
172
279
|
|
|
173
|
-
|
|
280
|
+
See [docs/rate-limiting.md](./docs/rate-limiting.md).
|
|
174
281
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
282
|
+
### Logging
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { createLoggingMiddleware, setLoggingStorage, MemoryLoggingStorage } from 'hono-crud';
|
|
286
|
+
|
|
287
|
+
setLoggingStorage(new MemoryLoggingStorage());
|
|
288
|
+
|
|
289
|
+
app.use('*', createLoggingMiddleware({
|
|
290
|
+
redactHeaders: ['authorization', 'cookie'],
|
|
291
|
+
redactBodyFields: ['password'],
|
|
292
|
+
}));
|
|
180
293
|
```
|
|
181
294
|
|
|
182
|
-
|
|
183
|
-
|
|
295
|
+
See [docs/logging.md](./docs/logging.md).
|
|
296
|
+
|
|
297
|
+
## Advanced Features
|
|
298
|
+
|
|
299
|
+
- **Soft Delete & Restore** - `softDelete: true` in model, `?withDeleted=true`, restore endpoint
|
|
300
|
+
- **Relations** - `hasOne`, `hasMany`, `belongsTo` with `?include=posts,profile`
|
|
301
|
+
- **Nested Writes** - Create/update related records in a single request
|
|
302
|
+
- **Batch Operations** - Batch create, update, delete, restore, upsert
|
|
303
|
+
- **Upsert** - Create or update by unique keys
|
|
304
|
+
- **Versioning** - Record version history with rollback
|
|
305
|
+
- **Audit Logging** - Track who changed what and when
|
|
306
|
+
- **Full-Text Search** - Weighted search with highlighting
|
|
307
|
+
- **Aggregation** - Sum, count, avg, min, max with grouping
|
|
308
|
+
- **Export/Import** - CSV and JSON export/import
|
|
309
|
+
- **Computed Fields** - Virtual fields calculated on read
|
|
310
|
+
- **Field Selection** - `?fields=id,name,email`
|
|
311
|
+
- **Events & Webhooks** - Event emitter with webhook delivery
|
|
312
|
+
- **Encryption** - Field-level encryption with Web Crypto API
|
|
313
|
+
- **Idempotency** - Idempotency key middleware for safe retries
|
|
314
|
+
- **Multi-Tenancy** - Tenant isolation via header, path, query, or JWT
|
|
315
|
+
- **Health Checks** - Liveness and readiness endpoints
|
|
316
|
+
- **Error Handling** - Typed exceptions with custom error handlers
|
|
317
|
+
|
|
318
|
+
See [docs/advanced-features.md](./docs/advanced-features.md) for examples of every feature.
|
|
319
|
+
|
|
320
|
+
## API Documentation
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import { setupSwaggerUI, setupReDoc, setupScalar } from 'hono-crud';
|
|
324
|
+
|
|
325
|
+
// OpenAPI spec
|
|
326
|
+
app.doc('/openapi.json', {
|
|
327
|
+
openapi: '3.1.0',
|
|
328
|
+
info: { title: 'My API', version: '1.0.0' },
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Documentation UIs
|
|
332
|
+
setupSwaggerUI(app, { docsPath: '/docs', specPath: '/openapi.json' });
|
|
333
|
+
setupReDoc(app, { redocPath: '/redoc', specPath: '/openapi.json' });
|
|
334
|
+
setupScalar(app, '/reference', { specUrl: '/openapi.json' });
|
|
335
|
+
```
|
|
184
336
|
|
|
185
337
|
## Examples
|
|
186
338
|
|
|
187
|
-
|
|
339
|
+
See the [examples/](./examples) directory for complete working applications:
|
|
188
340
|
|
|
189
|
-
- [Memory Adapter
|
|
190
|
-
- [Drizzle
|
|
191
|
-
- [Prisma
|
|
341
|
+
- [Memory Adapter](./examples/memory) - Basic CRUD, alternative APIs, comprehensive features
|
|
342
|
+
- [Drizzle + PostgreSQL](./examples/drizzle) - Schema, relations, filtering, batch operations
|
|
343
|
+
- [Prisma + PostgreSQL](./examples/prisma) - Schema, relations, filtering, batch operations
|
|
192
344
|
|
|
193
345
|
## Requirements
|
|
194
346
|
|
|
195
|
-
- Node.js >=
|
|
196
|
-
- TypeScript >= 5.0
|
|
347
|
+
- Node.js >= 20
|
|
348
|
+
- TypeScript >= 5.0
|
|
197
349
|
|
|
198
350
|
## License
|
|
199
351
|
|