db-model-router 1.0.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 ADDED
@@ -0,0 +1,505 @@
1
+ # rest-router
2
+
3
+ A database-agnostic REST API generator for Node.js. Works with Express or ultimate-express (a high-performance drop-in replacement). Define a model, get a full CRUD API with filtering, pagination, and bulk operations — backed by any of 9 supported databases.
4
+
5
+ ## Supported Adapters
6
+
7
+ | Adapter | Module Key | Driver | Install |
8
+ | --------------------------------------------- | ------------- | ------------------------ | ---------------------------------------------------------------------- |
9
+ | [MySQL](#mysql-example) | `mysql` | mysql2 | `npm i db-model-router mysql2` |
10
+ | [PostgreSQL](./docs/adapters/postgres.md) | `postgres` | pg | `npm i db-model-router pg` |
11
+ | [SQLite3](./docs/adapters/sqlite3.md) | `sqlite3` | better-sqlite3 | `npm i db-model-router better-sqlite3` |
12
+ | [MongoDB](./docs/adapters/mongodb.md) | `mongodb` | mongodb | `npm i db-model-router mongodb` |
13
+ | [MSSQL](./docs/adapters/mssql.md) | `mssql` | mssql | `npm i db-model-router mssql` |
14
+ | [CockroachDB](./docs/adapters/cockroachdb.md) | `cockroachdb` | pg | `npm i db-model-router pg` |
15
+ | [Oracle](./docs/adapters/oracle.md) | `oracle` | oracledb | `npm i db-model-router oracledb` |
16
+ | [Redis](./docs/adapters/redis.md) | `redis` | ioredis | `npm i db-model-router ioredis` |
17
+ | [DynamoDB](./docs/adapters/dynamodb.md) | `dynamodb` | @aws-sdk/client-dynamodb | `npm i db-model-router @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb` |
18
+
19
+ ## Installation
20
+
21
+ Install the core package, your preferred Express framework, and the driver for your database:
22
+
23
+ ```bash
24
+ # Pick your Express framework (one of the two)
25
+ npm install express
26
+ # OR for ~6x faster performance:
27
+ npm install ultimate-express
28
+
29
+ # Then install db-model-router + your database driver:
30
+
31
+ # MySQL (default)
32
+ npm install db-model-router mysql2
33
+
34
+ # PostgreSQL / CockroachDB
35
+ npm install db-model-router pg
36
+
37
+ # SQLite3
38
+ npm install db-model-router better-sqlite3
39
+
40
+ # MongoDB
41
+ npm install db-model-router mongodb
42
+
43
+ # MSSQL
44
+ npm install db-model-router mssql
45
+
46
+ # Oracle
47
+ npm install db-model-router oracledb
48
+
49
+ # Redis
50
+ npm install db-model-router ioredis
51
+
52
+ # DynamoDB
53
+ npm install db-model-router @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
54
+ ```
55
+
56
+ Both `express` and `ultimate-express` are optional peer dependencies. The library auto-detects which one is installed (preferring `ultimate-express` when both are present). All database drivers are also optional peer dependencies, so your `node_modules` stays lean.
57
+
58
+ ## MySQL Example
59
+
60
+ ### 1. Connect
61
+
62
+ ```js
63
+ const { init, db, model, route } = require("db-model-router");
64
+
65
+ // Default adapter is mysql, so init() is optional
66
+ db.connect({
67
+ host: "localhost",
68
+ port: 3306,
69
+ user: "root",
70
+ password: "password",
71
+ database: "my_app",
72
+ connectionLimit: 100,
73
+ charset: "utf8mb4",
74
+ });
75
+ ```
76
+
77
+ ### 2. Define a Model
78
+
79
+ ```js
80
+ const users = model(
81
+ db,
82
+ "users", // table name
83
+ {
84
+ // schema definition
85
+ id: "integer", // auto-increment PK (excluded from inserts)
86
+ name: "required|string",
87
+ email: "required|string",
88
+ age: "required|integer",
89
+ meta: "object", // stored as JSON
90
+ is_deleted: "boolean",
91
+ },
92
+ "id", // primary key column
93
+ ["id"], // unique key columns
94
+ { safeDelete: "is_deleted" }, // optional: soft-delete column
95
+ );
96
+ ```
97
+
98
+ Schema types: `string`, `integer`, `boolean`, `object`. Prefix with `required|` to enforce on insert/update.
99
+
100
+ ### 3. Mount REST Routes
101
+
102
+ ```js
103
+ // Works with either express or ultimate-express
104
+ const express = require("express"); // or require("ultimate-express")
105
+ const app = express();
106
+ app.use(express.json());
107
+
108
+ app.use("/users", route(users));
109
+ app.listen(3000);
110
+ ```
111
+
112
+ This creates 8 endpoints:
113
+
114
+ | Method | Path | Description |
115
+ | ------ | ------------ | ------------------------------- |
116
+ | GET | `/users/:id` | Get one record by PK |
117
+ | POST | `/users/add` | Insert a single record |
118
+ | PUT | `/users/:id` | Update a single record |
119
+ | DELETE | `/users/:id` | Delete a single record |
120
+ | GET | `/users/` | List with pagination |
121
+ | POST | `/users/` | Bulk insert (`{ data: [...] }`) |
122
+ | PUT | `/users/` | Bulk update (`{ data: [...] }`) |
123
+ | DELETE | `/users/` | Bulk delete |
124
+
125
+ ### 4. Payload Override
126
+
127
+ Inject values from the request into every payload (useful for multi-tenant apps):
128
+
129
+ ```js
130
+ app.use((req, res, next) => {
131
+ req.user = { user_id: 42 };
132
+ next();
133
+ });
134
+
135
+ app.use("/users", route(users, { user_id: "user.user_id" }));
136
+ ```
137
+
138
+ Every insert/update/query will have `user_id` set from `req.user.user_id`.
139
+
140
+ ## Model API
141
+
142
+ All model methods are async (except SQLite3 which is synchronous under the hood).
143
+
144
+ ### insert(data)
145
+
146
+ ```js
147
+ // Single insert — returns the full inserted record
148
+ const user = await users.insert({
149
+ name: "Alice",
150
+ email: "alice@example.com",
151
+ age: 30,
152
+ });
153
+ // => { id: 1, name: "Alice", email: "alice@example.com", age: 30 }
154
+
155
+ // Bulk insert — returns row count
156
+ const result = await users.insert({
157
+ data: [
158
+ { name: "Bob", email: "bob@example.com", age: 25 },
159
+ { name: "Charlie", email: "charlie@example.com", age: 35 },
160
+ ],
161
+ });
162
+ // => { rows: 2, message: "2 Userss are saved", type: "success" }
163
+ ```
164
+
165
+ ### update(data)
166
+
167
+ ```js
168
+ // Single update — returns the updated record
169
+ const updated = await users.update({
170
+ id: 1,
171
+ name: "Alice Updated",
172
+ email: "alice_v2@example.com",
173
+ age: 31,
174
+ });
175
+
176
+ // Bulk update
177
+ const result = await users.update({
178
+ data: [
179
+ { id: 1, name: "Alice V3", email: "alice@example.com", age: 32 },
180
+ { id: 2, name: "Bob V2", email: "bob@example.com", age: 26 },
181
+ ],
182
+ });
183
+ ```
184
+
185
+ ### byId(id)
186
+
187
+ ```js
188
+ const user = await users.byId(1);
189
+ // => { id: 1, name: "Alice", ... } or null
190
+ ```
191
+
192
+ ### find(filter)
193
+
194
+ ```js
195
+ const result = await users.find({ name: "Alice" });
196
+ // => { data: [{ id: 1, name: "Alice", ... }], count: 1 }
197
+ ```
198
+
199
+ ### findOne(filter)
200
+
201
+ ```js
202
+ const user = await users.findOne({ email: "alice@example.com" });
203
+ // => { id: 1, ... } or false
204
+ ```
205
+
206
+ ### list(options)
207
+
208
+ ```js
209
+ const page = await users.list({ page: 0, size: 10 });
210
+ // => { data: [...], count: 100 }
211
+
212
+ // With filter
213
+ const filtered = await users.list({ name: "Ali", page: 0 });
214
+ ```
215
+
216
+ ### remove(idOrFilter)
217
+
218
+ ```js
219
+ // By ID
220
+ await users.remove(1);
221
+
222
+ // By filter
223
+ await users.remove({ name: "Bob" });
224
+ ```
225
+
226
+ ## Filter System
227
+
228
+ Filters use a nested array structure: `[OR_groups[AND_conditions[column, operator, value]]]`
229
+
230
+ Supported operators: `=`, `like`, `not like`, `in`, `not in`, `<`, `>`, `<=`, `>=`, `!=`
231
+
232
+ ```js
233
+ // Find users named Alice OR aged > 30
234
+ const result = await db.get("users", [
235
+ [["name", "=", "Alice"]],
236
+ [["age", ">", 30]],
237
+ ]);
238
+
239
+ // Find users named Alice AND aged 30
240
+ const result = await db.get("users", [
241
+ [
242
+ ["name", "=", "Alice"],
243
+ ["age", "=", 30],
244
+ ],
245
+ ]);
246
+ ```
247
+
248
+ ## Switching Adapters
249
+
250
+ To use a different database, call `init()` before `db.connect()`:
251
+
252
+ ```js
253
+ const { init, db, model, route } = require("db-model-router");
254
+
255
+ init("postgres"); // or "mongodb", "sqlite3", "mssql", etc.
256
+
257
+ db.connect({
258
+ host: "localhost",
259
+ port: 5432,
260
+ user: "postgres",
261
+ password: "password",
262
+ database: "my_app",
263
+ });
264
+ ```
265
+
266
+ The model and route APIs remain identical across all adapters. See the individual adapter docs for connection options:
267
+
268
+ - [PostgreSQL](./docs/adapters/postgres.md)
269
+ - [SQLite3](./docs/adapters/sqlite3.md)
270
+ - [MongoDB](./docs/adapters/mongodb.md)
271
+ - [MSSQL](./docs/adapters/mssql.md)
272
+ - [CockroachDB](./docs/adapters/cockroachdb.md)
273
+ - [Oracle](./docs/adapters/oracle.md)
274
+ - [Redis](./docs/adapters/redis.md)
275
+ - [DynamoDB](./docs/adapters/dynamodb.md)
276
+
277
+ ## CLI Tools
278
+
279
+ Three CLI commands are included to scaffold models, routes, and full apps from an existing database.
280
+
281
+ ### generate-app
282
+
283
+ The fastest way to go from database to running API. Scaffolds a complete Express REST API project in a single command — introspects your database, generates models and routes (including parent-child relationships), and creates the app entry point, middleware, environment config, and project structure.
284
+
285
+ ```bash
286
+ # Full app from MySQL
287
+ rest-router-generate-app --type mysql --env .env
288
+
289
+ # SQLite3 into a specific directory
290
+ rest-router-generate-app --type sqlite3 --database ./myapp.db --output ./my-api
291
+
292
+ # Postgres with specific tables and relationships
293
+ rest-router-generate-app --type postgres --env .env --tables users,posts,posts.comments
294
+ ```
295
+
296
+ Options:
297
+
298
+ | Option | Description |
299
+ | ------------ | ------------------------------------------------------------------------------- |
300
+ | `--type` | Database type (mysql, postgres, sqlite3, mssql, oracle, cockroachdb) [required] |
301
+ | `--output` | Output directory (default: current directory) |
302
+ | `--host` | Database host |
303
+ | `--port` | Database port |
304
+ | `--database` | Database name or file path |
305
+ | `--user` | Database user |
306
+ | `--password` | Database password |
307
+ | `--schema` | Schema name (postgres only) |
308
+ | `--tables` | Comma-separated tables, supports `parent.child` notation for relationships |
309
+ | `--env` | Path to .env file for DB connection |
310
+
311
+ Generated project structure:
312
+
313
+ ```
314
+ my-api/
315
+ app.js # Express app with init(), db.connect(), middleware, error handler, health check
316
+ .env.example # Pre-filled environment template for your DB type
317
+ .gitignore # node_modules, .env, *.db
318
+ middleware/
319
+ logger.js # Request logger (method, URL, status, response time)
320
+ models/
321
+ users.js # Auto-generated from DB introspection
322
+ posts.js
323
+ index.js
324
+ routes/
325
+ users.js # Auto-generated route files
326
+ posts.js
327
+ index.js
328
+ openapi.json # OpenAPI 3.0 spec
329
+ migrations/
330
+ README.md # Placeholder for migration scripts
331
+ sessions/
332
+ README.md # Placeholder for session config
333
+ ```
334
+
335
+ When `--tables` includes parent-child notation (e.g., `posts.comments`), the routes directory also includes scoped child route files and nested route mounting — see [Parent-Child Relationships](#parent-child-relationships-foreign-keys) below.
336
+
337
+ To start the generated app:
338
+
339
+ ```bash
340
+ cp .env.example .env # edit with your DB credentials
341
+ npm install
342
+ node app.js
343
+ ```
344
+
345
+ ### generate-model
346
+
347
+ Connects to your database, introspects all tables, and generates model files with validation rules, primary keys, unique constraints, and auto-detected options (safeDelete, timestamps). This is called automatically by `generate-app`, but can be used standalone.
348
+
349
+ ```bash
350
+ # Basic usage
351
+ rest-router-generate-model --type mysql --host localhost --database mydb --user root --password secret
352
+
353
+ # Using an .env file
354
+ rest-router-generate-model --type postgres --env .env --output ./src/models
355
+
356
+ # SQLite3
357
+ rest-router-generate-model --type sqlite3 --database ./myapp.db --output ./models
358
+
359
+ # Only specific tables
360
+ rest-router-generate-model --type mysql --env .env --tables users,posts,comments
361
+ ```
362
+
363
+ Options:
364
+
365
+ | Option | Description |
366
+ | ------------ | ----------------------------------------------------------------------------- |
367
+ | `--type` | Database type (mysql, postgres, sqlite3, mssql, oracle, cockroachdb) |
368
+ | `--host` | Database host (default: localhost) |
369
+ | `--port` | Database port |
370
+ | `--database` | Database name (or file path for sqlite3) |
371
+ | `--user` | Database user |
372
+ | `--password` | Database password |
373
+ | `--schema` | Schema name (postgres only, default: public) |
374
+ | `--output` | Output directory (default: ./models) |
375
+ | `--tables` | Comma-separated list of tables to generate (supports `parent.child` notation) |
376
+ | `--env` | Path to .env file to load |
377
+
378
+ Auto-detection:
379
+
380
+ - Columns with `DEFAULT` values are marked as optional (not `required`)
381
+ - Timestamp columns (`created_at`, `updated_at`, `modified_at`, `createdAt`, etc.) are excluded from the model structure and added to the option object
382
+ - Soft-delete columns (`is_deleted`, `deleted`, `is_active`, `archived`, etc.) are excluded from the structure and set as `safeDelete` in the option
383
+ - Multi-column unique indexes are correctly grouped for the unique constraint parameter
384
+
385
+ Generated output:
386
+
387
+ ```
388
+ models/
389
+ users.js # model(db, "users", {...}, "user_id", ["email"], { safeDelete: "is_deleted", ... })
390
+ posts.js # model(db, "posts", {...}, "post_id", ["post_id"])
391
+ index.js # exports { users, posts }
392
+ ```
393
+
394
+ ### generate-route
395
+
396
+ Generates Express route files for each model. If models don't exist yet, it auto-generates them first. Also generates an OpenAPI 3.0 spec (`openapi.json`) from the model metadata. This is called automatically by `generate-app`, but can be used standalone.
397
+
398
+ ```bash
399
+ # From existing models
400
+ rest-router-generate-route --models ./models --output ./routes
401
+
402
+ # Auto-generate models + routes in one step
403
+ rest-router-generate-route --type mysql --env .env --models ./models --output ./routes
404
+
405
+ # SQLite3 one-liner
406
+ rest-router-generate-route --type sqlite3 --database ./myapp.db
407
+ ```
408
+
409
+ Options:
410
+
411
+ | Option | Description |
412
+ | ------------ | -------------------------------------------------------------------------- |
413
+ | `--models` | Path to models directory (default: ./models) |
414
+ | `--output` | Output directory for routes (default: ./routes) |
415
+ | `--type` | Database type — triggers model generation if models are missing |
416
+ | `--host` | Database host (passed to model generation) |
417
+ | `--port` | Database port (passed to model generation) |
418
+ | `--database` | Database name or file path (passed to model generation) |
419
+ | `--user` | Database user (passed to model generation) |
420
+ | `--password` | Database password (passed to model generation) |
421
+ | `--schema` | Schema name (passed to model generation) |
422
+ | `--tables` | Comma-separated tables, supports `parent.child` notation for relationships |
423
+ | `--env` | Path to .env file (passed to model generation) |
424
+
425
+ #### Parent-Child Relationships (Foreign Keys)
426
+
427
+ Use dot notation in `--tables` to declare parent-child relationships. This works in `generate-route`, `generate-app`, and `generate-model`. The generator creates nested routes that automatically scope child queries by the parent's foreign key.
428
+
429
+ ```bash
430
+ # Declare that comments belong to posts
431
+ rest-router-generate-route --type mysql --env .env --tables users,posts,posts.comments
432
+
433
+ # Same via generate-app
434
+ rest-router-generate-app --type mysql --env .env --tables users,posts,posts.comments
435
+ ```
436
+
437
+ The FK column is derived by convention: `<parent_singular>_id` (e.g., `posts.comments` → `post_id`).
438
+
439
+ This generates:
440
+
441
+ ```
442
+ routes/
443
+ users.js # route(users)
444
+ posts.js # route(posts)
445
+ comments.js # route(comments) — direct access
446
+ comments_child_of_posts.js # route(comments, { post_id: "params.post_id" }) — scoped
447
+ index.js # mounts all routes
448
+ openapi.json # OpenAPI 3.0 spec
449
+ ```
450
+
451
+ The generated `index.js` mounts both nested and top-level routes:
452
+
453
+ ```js
454
+ // Nested: GET /api/posts/:post_id/comments — returns only comments for that post
455
+ router.use("/posts/:post_id/comments", commentsChildRoute);
456
+
457
+ // Direct: GET /api/comments — returns all comments
458
+ router.use("/comments", commentsRoute);
459
+ ```
460
+
461
+ The child route file uses payload override to scope every query:
462
+
463
+ ```js
464
+ // comments_child_of_posts.js
465
+ module.exports = route(comments, { post_id: "params.post_id" });
466
+ ```
467
+
468
+ #### Generated output (without relationships)
469
+
470
+ ```
471
+ routes/
472
+ users.js # route(users)
473
+ posts.js # route(posts)
474
+ index.js # express.Router() mounting all routes at /users, /posts, etc.
475
+ openapi.json # OpenAPI 3.0 spec
476
+ ```
477
+
478
+ ## Environment Setup (Docker)
479
+
480
+ A `docker-compose.yml` is included for running all supported databases locally:
481
+
482
+ ```bash
483
+ docker compose up -d
484
+ ```
485
+
486
+ Per-adapter `.env` files live in `env/`:
487
+
488
+ ```bash
489
+ npm run test:mysql # uses env/.env.mysql
490
+ npm run test:postgres # uses env/.env.postgres
491
+ npm run test:mongodb # uses env/.env.mongodb
492
+ npm run test:redis # uses env/.env.redis
493
+ npm run test:mssql # uses env/.env.mssql
494
+ npm run test:cockroachdb # uses env/.env.cockroachdb
495
+ npm run test:dynamodb # uses env/.env.dynamodb
496
+ npm run test:sqlite3 # uses env/.env.sqlite3 (in-memory, no Docker needed)
497
+ ```
498
+
499
+ ## License
500
+
501
+ Apache-2.0
502
+
503
+ ## LLM Skill Reference
504
+
505
+ For AI/LLM integration, see the [Skill Reference](./docs/SKILL.md) — a structured document covering the full API surface, patterns, constraints, and connection configs for all adapters.
@@ -0,0 +1,141 @@
1
+ networks:
2
+ db-network:
3
+ driver: bridge
4
+
5
+ services:
6
+ mysql:
7
+ image: mysql:8.0
8
+ container_name: rest-router-mysql
9
+ restart: unless-stopped
10
+ ports:
11
+ - "3306:3306"
12
+ environment:
13
+ MYSQL_ROOT_PASSWORD: password
14
+ MYSQL_DATABASE: test_db
15
+ command: --default-authentication-plugin=mysql_native_password
16
+ healthcheck:
17
+ test:
18
+ ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-ppassword"]
19
+ interval: 10s
20
+ timeout: 5s
21
+ retries: 5
22
+ start_period: 30s
23
+ networks:
24
+ - db-network
25
+
26
+ postgres:
27
+ image: postgres:16
28
+ container_name: rest-router-postgres
29
+ restart: unless-stopped
30
+ ports:
31
+ - "5432:5432"
32
+ environment:
33
+ POSTGRES_USER: postgres
34
+ POSTGRES_PASSWORD: password
35
+ POSTGRES_DB: test_db
36
+ healthcheck:
37
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
38
+ interval: 10s
39
+ timeout: 5s
40
+ retries: 5
41
+ start_period: 15s
42
+ networks:
43
+ - db-network
44
+
45
+ mongodb:
46
+ image: mongo:7
47
+ container_name: rest-router-mongodb
48
+ restart: unless-stopped
49
+ ports:
50
+ - "27017:27017"
51
+ healthcheck:
52
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
53
+ interval: 10s
54
+ timeout: 5s
55
+ retries: 5
56
+ start_period: 15s
57
+ networks:
58
+ - db-network
59
+
60
+ redis:
61
+ image: redis:7
62
+ container_name: rest-router-redis
63
+ restart: unless-stopped
64
+ ports:
65
+ - "6379:6379"
66
+ healthcheck:
67
+ test: ["CMD", "redis-cli", "ping"]
68
+ interval: 10s
69
+ timeout: 5s
70
+ retries: 5
71
+ start_period: 10s
72
+ networks:
73
+ - db-network
74
+
75
+ cockroachdb:
76
+ image: cockroachdb/cockroach:latest
77
+ container_name: rest-router-cockroachdb
78
+ restart: unless-stopped
79
+ ports:
80
+ - "26257:26257"
81
+ - "8080:8080"
82
+ command: start-single-node --insecure
83
+ healthcheck:
84
+ test: ["CMD", "curl", "-f", "http://localhost:8080/health?ready=1"]
85
+ interval: 10s
86
+ timeout: 5s
87
+ retries: 5
88
+ start_period: 20s
89
+ networks:
90
+ - db-network
91
+
92
+ mssql:
93
+ image: mcr.microsoft.com/mssql/server
94
+ container_name: rest-router-mssql
95
+ restart: unless-stopped
96
+ ports:
97
+ - "1433:1433"
98
+ environment:
99
+ ACCEPT_EULA: "Y"
100
+ MSSQL_SA_PASSWORD: "Password123!"
101
+ healthcheck:
102
+ test: ["CMD-SHELL", "/opt/mssql-tools*/bin/sqlcmd -S localhost -U sa -P 'Password123!' -Q 'SELECT 1' || exit 1"]
103
+ interval: 10s
104
+ timeout: 5s
105
+ retries: 10
106
+ start_period: 30s
107
+ networks:
108
+ - db-network
109
+
110
+ dynamodb:
111
+ image: amazon/dynamodb-local
112
+ container_name: rest-router-dynamodb
113
+ restart: unless-stopped
114
+ ports:
115
+ - "8000:8000"
116
+ command: -jar DynamoDBLocal.jar -sharedDb
117
+ healthcheck:
118
+ test: ["CMD-SHELL", "curl -f http://localhost:8000 || exit 1"]
119
+ interval: 10s
120
+ timeout: 5s
121
+ retries: 5
122
+ start_period: 10s
123
+ networks:
124
+ - db-network
125
+
126
+ oracle:
127
+ image: gvenzl/oracle-xe:21-slim
128
+ container_name: rest-router-oracle
129
+ restart: unless-stopped
130
+ ports:
131
+ - "1521:1521"
132
+ environment:
133
+ ORACLE_PASSWORD: oracle
134
+ healthcheck:
135
+ test: ["CMD-SHELL", "healthcheck.sh"]
136
+ interval: 30s
137
+ timeout: 10s
138
+ retries: 10
139
+ start_period: 90s
140
+ networks:
141
+ - db-network