gazan-init 0.1.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/LICENSE +21 -0
- package/README.md +722 -0
- package/bin/gazan.js +17 -0
- package/package.json +54 -0
- package/src/cli/commands/init.js +126 -0
- package/src/cli/index.js +22 -0
- package/src/config/aliases.js +130 -0
- package/src/config/normalize.js +65 -0
- package/src/generators/aliasResolver.js +68 -0
- package/src/generators/aliasRuntime.js +79 -0
- package/src/generators/database/mongoNative.js +45 -0
- package/src/generators/database/mongoose.js +32 -0
- package/src/generators/database/prisma.js +61 -0
- package/src/generators/engine.js +88 -0
- package/src/generators/entity/sensitiveFields.js +12 -0
- package/src/generators/entity/toApp.js +325 -0
- package/src/generators/entity/toMongoose.js +180 -0
- package/src/generators/entity/toPrisma.js +233 -0
- package/src/generators/entity/toZod.js +70 -0
- package/src/generators/env.js +123 -0
- package/src/generators/errors.js +67 -0
- package/src/generators/features/auth.js +163 -0
- package/src/generators/features/bullmq.js +96 -0
- package/src/generators/features/redis.js +34 -0
- package/src/generators/features/socket.js +45 -0
- package/src/generators/gitignore.js +19 -0
- package/src/generators/index.js +317 -0
- package/src/generators/middlewares.js +167 -0
- package/src/generators/packageJson.js +107 -0
- package/src/generators/paths.js +45 -0
- package/src/generators/project.js +189 -0
- package/src/generators/readme.js +177 -0
- package/src/generators/shutdown.js +113 -0
- package/src/generators/stripSensitiveFieldsHelper.js +50 -0
- package/src/generators/syntax.js +65 -0
- package/src/generators/tsconfig.js +32 -0
- package/src/parser/entity/errors.js +21 -0
- package/src/parser/entity/parse.js +417 -0
- package/src/parser/entity/schema.js +104 -0
- package/src/prompts/index.js +186 -0
- package/src/utils/fsSafety.js +17 -0
- package/src/utils/strings.js +110 -0
package/README.md
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
<p align="center"><strong>GAZAN</strong></p>
|
|
2
|
+
<p align="center">Interactive backend project generator for Node.js</p>
|
|
3
|
+
|
|
4
|
+
<p align="center">
|
|
5
|
+
<img alt="license" src="https://img.shields.io/badge/license-MIT-blue.svg">
|
|
6
|
+
<img alt="node" src="https://img.shields.io/badge/node-%3E%3D18-brightgreen">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
GAZAN is a CLI that asks a short series of questions about the backend you want — module
|
|
10
|
+
system, language, architecture, database, auth, infrastructure — and generates a complete,
|
|
11
|
+
runnable Express project from the answers. Nothing you don't select gets generated: no unused
|
|
12
|
+
dependencies, no dead directories, no disabled-feature code paths.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx gazan-init init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Why GAZAN?
|
|
19
|
+
|
|
20
|
+
Most "backend starter" templates are frozen snapshots: one language, one database, one
|
|
21
|
+
architecture, and you delete what you don't need. GAZAN generates the project *from* your
|
|
22
|
+
choices instead, so the result only ever contains what you asked for — and the generator itself
|
|
23
|
+
is tested against dozens of real configuration combinations, not just the default one.
|
|
24
|
+
|
|
25
|
+
It is not a repository-layer framework, an ORM, or a runtime library your generated project
|
|
26
|
+
depends on. Once generated, the project is a plain Express app with no reference back to GAZAN.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **CommonJS or ES Modules**, **JavaScript or TypeScript** — correct syntax, extensions, and
|
|
31
|
+
`tsconfig.json`/`package.json` wiring for whichever combination you pick.
|
|
32
|
+
- **MVC or HMVC** architecture, with or without a `src/` directory.
|
|
33
|
+
- **PostgreSQL + Prisma**, **MongoDB + Mongoose**, **MongoDB (native driver)**, or no database.
|
|
34
|
+
- **`entity.json`** — an optional schema file that, when provided, generates real Prisma models
|
|
35
|
+
or Mongoose schemas plus matching Zod validators, services, controllers, and routes.
|
|
36
|
+
- **Redis, BullMQ, Socket.IO** — each generated only when selected, sharing one Redis connection.
|
|
37
|
+
- **Authentication** — email/password (bcrypt), JWT, refresh tokens; OAuth is a documented stub
|
|
38
|
+
(see [Authentication](#authentication)).
|
|
39
|
+
- **Module aliases** (`@/services/x` instead of `../../services/x`) — optional, generated for
|
|
40
|
+
whichever directories your configuration actually produces, and wired to actually resolve at
|
|
41
|
+
runtime for every module system / language combination (see [Module Aliases](#module-aliases)).
|
|
42
|
+
- **Security baseline** — Helmet, CORS, request validation (Zod), rate limiting (in-memory or
|
|
43
|
+
Redis-backed), centralized error handling, environment validation at startup, graceful shutdown,
|
|
44
|
+
and automatic stripping of password/secret-like fields from API responses.
|
|
45
|
+
- **No repository layer.** Services talk to the database directly:
|
|
46
|
+
`Route → Controller → Service → Database`.
|
|
47
|
+
|
|
48
|
+
## Supported stack
|
|
49
|
+
|
|
50
|
+
| Concern | Options |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Module system | CommonJS, ES Modules |
|
|
53
|
+
| Language | JavaScript, TypeScript |
|
|
54
|
+
| Architecture | MVC, HMVC |
|
|
55
|
+
| Database | PostgreSQL + Prisma, MongoDB + Mongoose, MongoDB (native driver), none |
|
|
56
|
+
| Infrastructure | Redis, BullMQ, Socket.IO |
|
|
57
|
+
| Authentication | Email/password, JWT, refresh tokens, OAuth (stub) |
|
|
58
|
+
| Module aliases | Optional, on by default |
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx gazan-init init
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or install the CLI globally:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install -g gazan-init
|
|
70
|
+
gazan init
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
From a local clone:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone git@github.com:korveniq/Gazan.git
|
|
77
|
+
cd Gazan
|
|
78
|
+
npm install
|
|
79
|
+
node bin/gazan.js init
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Requires **Node.js 18 or later** — `gazan` checks this itself at startup and exits with a clear
|
|
83
|
+
message if your Node version is too old.
|
|
84
|
+
|
|
85
|
+
## Quick start
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
node bin/gazan.js init
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
GAZAN asks its questions in order, then generates the project:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
┌ GAZAN — backend project initializer
|
|
95
|
+
│
|
|
96
|
+
◇ Project name
|
|
97
|
+
│ my-api
|
|
98
|
+
│
|
|
99
|
+
◇ Which module system do you want?
|
|
100
|
+
│ ES Modules (MJS)
|
|
101
|
+
│
|
|
102
|
+
◇ Which language do you want?
|
|
103
|
+
│ TypeScript
|
|
104
|
+
│
|
|
105
|
+
◇ Do you want to enable module/path aliases? (e.g. @/services/x instead of ../../services/x)
|
|
106
|
+
│ Yes
|
|
107
|
+
│
|
|
108
|
+
◇ Which database do you want?
|
|
109
|
+
│ PostgreSQL + Prisma
|
|
110
|
+
│
|
|
111
|
+
◇ Which architecture do you want?
|
|
112
|
+
│ MVC
|
|
113
|
+
│
|
|
114
|
+
◇ Use src/ directory?
|
|
115
|
+
│ Yes
|
|
116
|
+
│
|
|
117
|
+
◇ Do you need Socket.IO?
|
|
118
|
+
│ No
|
|
119
|
+
│
|
|
120
|
+
◇ Do you need BullMQ workers?
|
|
121
|
+
│ No
|
|
122
|
+
│
|
|
123
|
+
◇ Use Redis for rate limiting? (No = in-memory rate limiting)
|
|
124
|
+
│ No
|
|
125
|
+
│
|
|
126
|
+
◇ Do you need authentication?
|
|
127
|
+
│ Yes
|
|
128
|
+
│
|
|
129
|
+
◇ Which authentication methods?
|
|
130
|
+
│ JWT
|
|
131
|
+
│
|
|
132
|
+
◇ Do you have an entity.json file?
|
|
133
|
+
│ No
|
|
134
|
+
│
|
|
135
|
+
└ Configuration collected.
|
|
136
|
+
|
|
137
|
+
✔ Project structure created
|
|
138
|
+
✔ Environment & error handling configured
|
|
139
|
+
✔ Security middleware configured
|
|
140
|
+
✔ Database configured
|
|
141
|
+
✔ Redis configured
|
|
142
|
+
✔ BullMQ configured
|
|
143
|
+
✔ Socket.IO configured
|
|
144
|
+
✔ Authentication configured
|
|
145
|
+
✔ Entity models generated
|
|
146
|
+
✔ Module aliases configured
|
|
147
|
+
✔ README generated
|
|
148
|
+
|
|
149
|
+
Project initialized successfully.
|
|
150
|
+
|
|
151
|
+
Next steps:
|
|
152
|
+
|
|
153
|
+
cd my-api
|
|
154
|
+
npm install
|
|
155
|
+
npm run dev
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If the target directory already has content in it (beyond a stray `.git`), GAZAN asks before
|
|
159
|
+
touching anything — see [Existing directories](#existing-directories).
|
|
160
|
+
|
|
161
|
+
## Generated project structure
|
|
162
|
+
|
|
163
|
+
**MVC**, with `src/`, PostgreSQL + Prisma, Redis, and aliases enabled:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
my-api/
|
|
167
|
+
├── src/
|
|
168
|
+
│ ├── controllers/
|
|
169
|
+
│ ├── routes/
|
|
170
|
+
│ │ └── index.js
|
|
171
|
+
│ ├── services/
|
|
172
|
+
│ ├── validators/
|
|
173
|
+
│ ├── middlewares/
|
|
174
|
+
│ │ ├── error-handler.js
|
|
175
|
+
│ │ ├── not-found.js
|
|
176
|
+
│ │ ├── rate-limit.js
|
|
177
|
+
│ │ └── validate.js
|
|
178
|
+
│ ├── helpers/
|
|
179
|
+
│ │ ├── env.js
|
|
180
|
+
│ │ └── errors.js
|
|
181
|
+
│ ├── utils/
|
|
182
|
+
│ │ ├── shutdown.js
|
|
183
|
+
│ │ └── validate-env.js
|
|
184
|
+
│ ├── configs/
|
|
185
|
+
│ │ ├── db/
|
|
186
|
+
│ │ └── redis/
|
|
187
|
+
│ ├── app.js
|
|
188
|
+
│ └── server.js
|
|
189
|
+
├── prisma/
|
|
190
|
+
│ └── schema.prisma
|
|
191
|
+
├── jsconfig.json
|
|
192
|
+
├── .env
|
|
193
|
+
├── .env.example
|
|
194
|
+
├── .gitignore
|
|
195
|
+
├── package.json
|
|
196
|
+
└── README.md
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`workers/` only exists when BullMQ is enabled, `socket/` only when Socket.IO is enabled,
|
|
200
|
+
`prisma/` only for PostgreSQL + Prisma, `models/` only for MongoDB + Mongoose, `tsconfig.json`
|
|
201
|
+
only for TypeScript, and `jsconfig.json` / `alias-loader.mjs` only when aliases are enabled (see
|
|
202
|
+
[Module Aliases](#module-aliases) for which files appear for which module system).
|
|
203
|
+
|
|
204
|
+
**HMVC** groups `controllers/routes/services/validators` per entity instead of by layer:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
src/
|
|
208
|
+
├── modules/
|
|
209
|
+
│ ├── user/
|
|
210
|
+
│ │ ├── controllers/
|
|
211
|
+
│ │ ├── routes/
|
|
212
|
+
│ │ ├── services/
|
|
213
|
+
│ │ └── validators/
|
|
214
|
+
│ └── post/
|
|
215
|
+
│ ├── controllers/
|
|
216
|
+
│ ├── routes/
|
|
217
|
+
│ ├── services/
|
|
218
|
+
│ └── validators/
|
|
219
|
+
├── routes/
|
|
220
|
+
│ └── index.js # aggregates every module's router
|
|
221
|
+
├── middlewares/
|
|
222
|
+
├── helpers/
|
|
223
|
+
├── utils/
|
|
224
|
+
└── configs/
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Architecture
|
|
228
|
+
|
|
229
|
+
Both architectures follow the same request flow — **there is no repository layer**:
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
MVC: Route → Controller → Service → Database
|
|
233
|
+
HMVC: Module Route → Module Controller → Module Service → Database
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Controllers and services are classes. A controller is constructed with a service instance
|
|
237
|
+
(dependency injection) and contains no business logic; a service is the only thing that talks to
|
|
238
|
+
the database. Routes stay thin — they wire a validator and a controller method together and
|
|
239
|
+
nothing else.
|
|
240
|
+
|
|
241
|
+
## `entity.json`
|
|
242
|
+
|
|
243
|
+
An optional JSON file you can point GAZAN at during `init`. When provided, it becomes the source
|
|
244
|
+
of truth for generated database models, Zod validators, services, controllers, and routes — one
|
|
245
|
+
CRUD endpoint set per model (`POST /`, `GET /`, `GET /:id`, `PATCH /:id`, `DELETE /:id`), mounted
|
|
246
|
+
under `/api/<pluralized-model-name>`.
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
entity.json → Entity Parser (schema + semantic validation) → Normalized Entity Model → Database Generator
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Invalid files fail with precise, field-level errors — not a bare "invalid JSON":
|
|
253
|
+
|
|
254
|
+
```text
|
|
255
|
+
models[1].relations.author.model:
|
|
256
|
+
unknown model 'Userr'. Did you mean 'User'?
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Basic example
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"models": [
|
|
264
|
+
{
|
|
265
|
+
"name": "User",
|
|
266
|
+
"fields": {
|
|
267
|
+
"id": { "type": "uuid", "primaryKey": true, "default": "uuid" },
|
|
268
|
+
"name": { "type": "string", "required": true },
|
|
269
|
+
"email": { "type": "string", "required": true, "unique": true },
|
|
270
|
+
"age": { "type": "integer" }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Realistic example
|
|
278
|
+
|
|
279
|
+
A blog-shaped schema exercising UUID primary keys, an enum, unique + indexed fields, a nullable
|
|
280
|
+
optional relation, a one-to-many, and a many-to-many:
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"models": [
|
|
285
|
+
{
|
|
286
|
+
"name": "Role",
|
|
287
|
+
"fields": {
|
|
288
|
+
"id": { "type": "uuid", "primaryKey": true, "default": "uuid" },
|
|
289
|
+
"name": { "type": "string", "required": true, "unique": true }
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "User",
|
|
294
|
+
"fields": {
|
|
295
|
+
"id": { "type": "uuid", "primaryKey": true, "default": "uuid" },
|
|
296
|
+
"name": { "type": "string", "required": true, "length": 120 },
|
|
297
|
+
"email": { "type": "string", "required": true, "unique": true, "index": true },
|
|
298
|
+
"passwordHash": { "type": "string", "required": true },
|
|
299
|
+
"roleId": { "type": "uuid", "required": true }
|
|
300
|
+
},
|
|
301
|
+
"relations": {
|
|
302
|
+
"role": { "type": "belongsTo", "model": "Role", "foreignKey": "roleId" }
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"name": "Category",
|
|
307
|
+
"fields": {
|
|
308
|
+
"id": { "type": "uuid", "primaryKey": true, "default": "uuid" },
|
|
309
|
+
"name": { "type": "string", "required": true, "unique": true }
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"name": "Tag",
|
|
314
|
+
"fields": {
|
|
315
|
+
"id": { "type": "uuid", "primaryKey": true, "default": "uuid" },
|
|
316
|
+
"name": { "type": "string", "required": true, "unique": true }
|
|
317
|
+
},
|
|
318
|
+
"relations": {
|
|
319
|
+
"posts": { "type": "belongsToMany", "model": "Post", "through": "PostTag" }
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"name": "Post",
|
|
324
|
+
"fields": {
|
|
325
|
+
"id": { "type": "uuid", "primaryKey": true, "default": "uuid" },
|
|
326
|
+
"title": { "type": "string", "required": true, "length": 200, "index": true },
|
|
327
|
+
"slug": { "type": "string", "required": true, "unique": true },
|
|
328
|
+
"body": { "type": "text", "required": false, "nullable": true },
|
|
329
|
+
"status": { "type": "enum", "values": ["DRAFT", "PUBLISHED", "ARCHIVED"], "default": "DRAFT", "required": true },
|
|
330
|
+
"published": { "type": "boolean", "default": false, "required": true },
|
|
331
|
+
"authorId": { "type": "uuid", "required": true },
|
|
332
|
+
"categoryId": { "type": "uuid", "required": false, "nullable": true }
|
|
333
|
+
},
|
|
334
|
+
"relations": {
|
|
335
|
+
"author": { "type": "belongsTo", "model": "User", "foreignKey": "authorId" },
|
|
336
|
+
"category": { "type": "belongsTo", "model": "Category", "foreignKey": "categoryId" },
|
|
337
|
+
"comments": { "type": "hasMany", "model": "Comment", "foreignKey": "postId" },
|
|
338
|
+
"tags": { "type": "belongsToMany", "model": "Tag", "through": "PostTag" }
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"name": "Comment",
|
|
343
|
+
"fields": {
|
|
344
|
+
"id": { "type": "uuid", "primaryKey": true, "default": "uuid" },
|
|
345
|
+
"body": { "type": "text", "required": true },
|
|
346
|
+
"postId": { "type": "uuid", "required": true },
|
|
347
|
+
"authorId": { "type": "uuid", "required": true }
|
|
348
|
+
},
|
|
349
|
+
"relations": {
|
|
350
|
+
"post": { "type": "belongsTo", "model": "Post", "foreignKey": "postId" },
|
|
351
|
+
"author": { "type": "belongsTo", "model": "User", "foreignKey": "authorId" }
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Both examples above are copy-pasteable and are exercised directly by GAZAN's own test suite
|
|
359
|
+
against both Prisma and Mongoose.
|
|
360
|
+
|
|
361
|
+
### Field types
|
|
362
|
+
|
|
363
|
+
| Type | Description | PostgreSQL (Prisma) | MongoDB (Mongoose) |
|
|
364
|
+
|---|---|---|---|
|
|
365
|
+
| `string` | Short text | `String` | `String` |
|
|
366
|
+
| `text` | Long text | `String` | `String` |
|
|
367
|
+
| `number` | Generic number | `Float` | `Number` |
|
|
368
|
+
| `integer` | Whole number | `Int` | `Number` |
|
|
369
|
+
| `float` | Floating point | `Float` | `Number` |
|
|
370
|
+
| `boolean` | True/false | `Boolean` | `Boolean` |
|
|
371
|
+
| `date` | Calendar date | `DateTime` | `Date` |
|
|
372
|
+
| `datetime` | Date + time | `DateTime` | `Date` |
|
|
373
|
+
| `uuid` | UUID string | `String` (`@default(uuid())` when `default: "uuid"`) | `String` |
|
|
374
|
+
| `json` | Arbitrary JSON | `Json` | `Mixed` |
|
|
375
|
+
| `enum` | Fixed set of values (needs `values`) | Generated Prisma `enum` | `String` with a schema-level enum validator |
|
|
376
|
+
| `decimal` | Fixed-precision number | `Decimal` | `Decimal128` |
|
|
377
|
+
| `bigint` | Large integer | `BigInt` | `Mixed` (Mongoose has no dedicated bigint type) |
|
|
378
|
+
|
|
379
|
+
**MongoDB primary keys must be `uuid` or omitted.** Every model's primary key is routed through
|
|
380
|
+
Mongo's native `_id` rather than a redundant parallel field; GAZAN generates a UUID string default
|
|
381
|
+
for it. A primary key of any other type (or `autoIncrement`, which Mongo has no native equivalent
|
|
382
|
+
for) is rejected at validation time rather than silently approximated.
|
|
383
|
+
|
|
384
|
+
### Field attributes
|
|
385
|
+
|
|
386
|
+
| Attribute | Meaning |
|
|
387
|
+
|---|---|
|
|
388
|
+
| `required` | Field must be present; cannot be combined with `nullable: true`. |
|
|
389
|
+
| `nullable` | Column/field may be `null`. Not allowed on a `primaryKey` field. |
|
|
390
|
+
| `unique` | Unique constraint (`@unique` in Prisma, `unique: true` in Mongoose). |
|
|
391
|
+
| `index` | Single-column index (`@@index` in Prisma, `index: true` in Mongoose). |
|
|
392
|
+
| `default` | Default value. `"uuid"` is a sentinel meaning "generate one"; `"now"` on a `date`/`datetime` field means `now()`. |
|
|
393
|
+
| `primaryKey` | Marks the primary key. At most one per model. |
|
|
394
|
+
| `autoIncrement` | Postgres/Prisma only (`integer`/`bigint`); rejected for MongoDB. |
|
|
395
|
+
| `length` | Max string length (`@db.VarChar(n)` in Prisma, `maxlength` in Mongoose). |
|
|
396
|
+
| `min` / `max` | Numeric bounds, enforced in the generated Zod validator (and Mongoose `min`/`max`). |
|
|
397
|
+
| `values` | Required for `type: "enum"` — the list of allowed values. |
|
|
398
|
+
|
|
399
|
+
### Relations
|
|
400
|
+
|
|
401
|
+
Four relation types, declared under a model's `relations` object:
|
|
402
|
+
|
|
403
|
+
```json
|
|
404
|
+
{
|
|
405
|
+
"models": [
|
|
406
|
+
{
|
|
407
|
+
"name": "Post",
|
|
408
|
+
"fields": {
|
|
409
|
+
"authorId": { "type": "uuid", "required": true }
|
|
410
|
+
},
|
|
411
|
+
"relations": {
|
|
412
|
+
"author": { "type": "belongsTo", "model": "User", "foreignKey": "authorId" }
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
- **`belongsTo`** — this model holds the foreign key (`foreignKey` required).
|
|
420
|
+
- **`hasMany`** / **`hasOne`** — the *other* model holds the foreign key (`foreignKey` required).
|
|
421
|
+
You only need to declare this when you want to name or customize the reverse side yourself —
|
|
422
|
+
GAZAN auto-derives a reverse array field for any `belongsTo` that doesn't have one, including
|
|
423
|
+
disambiguating multiple relations to the same model and self-relations.
|
|
424
|
+
- **`belongsToMany`** — many-to-many (`through` required, naming the join). **Both models must
|
|
425
|
+
declare it, with a matching `through` value** — a one-sided declaration is rejected at
|
|
426
|
+
validation time rather than silently generating a broken schema.
|
|
427
|
+
|
|
428
|
+
**Many-to-many is schema-only.** `belongsToMany` is correctly represented in the generated Prisma
|
|
429
|
+
schema (implicit join table) and Mongoose model (array of refs on both sides), but the generated
|
|
430
|
+
CRUD `create`/`update` endpoints do not read or write it — Prisma's nested-write shape
|
|
431
|
+
(`connect: [...]`) and Mongoose's plain array differ enough that GAZAN doesn't attempt a one-size
|
|
432
|
+
implementation. Manage join-table writes through your own service code.
|
|
433
|
+
|
|
434
|
+
## Database support
|
|
435
|
+
|
|
436
|
+
| Mode | Notes |
|
|
437
|
+
|---|---|
|
|
438
|
+
| PostgreSQL + Prisma | `prisma/schema.prisma` generated from `entity.json` (or a starter model if none is given, so `prisma generate` works immediately). |
|
|
439
|
+
| MongoDB + Mongoose | Models under `src/models/`, `_id`-based primary keys (see above). |
|
|
440
|
+
| MongoDB (native driver) | No Mongoose — a thin `MongoClient` wrapper with a duplicate-connect guard. |
|
|
441
|
+
| None | No database code, no DB dependency, no `DATABASE_URL`/`MONGODB_URI` requirement. |
|
|
442
|
+
|
|
443
|
+
## Authentication
|
|
444
|
+
|
|
445
|
+
Selected independently — only what you pick is generated:
|
|
446
|
+
|
|
447
|
+
| Method | What's generated |
|
|
448
|
+
|---|---|
|
|
449
|
+
| Email/password | `helpers/bcrypt.js` (`hashPassword`/`comparePassword`) |
|
|
450
|
+
| JWT | `helpers/jwt.js` (`signAccessToken`/`verifyAccessToken`) + `middlewares/auth.js` |
|
|
451
|
+
| Refresh tokens | Adds `signRefreshToken`/`verifyRefreshToken` to `helpers/jwt.js` |
|
|
452
|
+
| OAuth | **Stub only** — see below |
|
|
453
|
+
|
|
454
|
+
> **OAuth is currently a documented integration stub.** GAZAN scaffolds
|
|
455
|
+
> `OAUTH_CLIENT_ID`/`OAUTH_CLIENT_SECRET`/`OAUTH_CALLBACK_URL` env vars and a
|
|
456
|
+
> `helpers/oauth.stub.*` file that documents what to build and throws if called — it does not
|
|
457
|
+
> provide a complete, provider-specific OAuth flow. No OAuth client library is installed. GAZAN
|
|
458
|
+
> prints an explicit warning about this after generation.
|
|
459
|
+
|
|
460
|
+
Password/secret/hash-like fields declared in `entity.json` (matching `/password|secret|hash/i`)
|
|
461
|
+
are automatically stripped from every generated CRUD response, regardless of database backend.
|
|
462
|
+
|
|
463
|
+
## Module aliases
|
|
464
|
+
|
|
465
|
+
Optional (on by default) — generated from whichever directories your specific configuration
|
|
466
|
+
actually produces, never for a feature you didn't enable.
|
|
467
|
+
|
|
468
|
+
**MVC**, PostgreSQL + Redis + Socket.IO + BullMQ:
|
|
469
|
+
|
|
470
|
+
```text
|
|
471
|
+
@ → src
|
|
472
|
+
@controllers → src/controllers
|
|
473
|
+
@services → src/services
|
|
474
|
+
@routes → src/routes
|
|
475
|
+
@validators → src/validators
|
|
476
|
+
@middlewares → src/middlewares
|
|
477
|
+
@utils → src/utils
|
|
478
|
+
@helpers → src/helpers
|
|
479
|
+
@configs → src/configs
|
|
480
|
+
@db → src/configs/db
|
|
481
|
+
@redis → src/configs/redis
|
|
482
|
+
@socket → src/socket
|
|
483
|
+
@workers → src/workers
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
import { prisma } from '@db/index';
|
|
488
|
+
import { UserService } from '@services/user.service';
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**HMVC** additionally gets one alias per entity model (pluralized, pointing at that model's real
|
|
492
|
+
module directory — e.g. a `User` model's module lives at `src/modules/user/`, aliased as
|
|
493
|
+
`@users`):
|
|
494
|
+
|
|
495
|
+
```text
|
|
496
|
+
@ → src
|
|
497
|
+
@users → src/modules/user
|
|
498
|
+
@posts → src/modules/post
|
|
499
|
+
@comments → src/modules/comment
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
import { UserService } from '@users/services/user.service';
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
If a model's pluralized name would collide with a reserved alias (e.g. a model literally named
|
|
507
|
+
`Service`), generation fails with a clear error rather than silently misresolving imports.
|
|
508
|
+
|
|
509
|
+
**How aliases actually resolve at runtime** — this differs by module system, and GAZAN picks the
|
|
510
|
+
mechanism for you:
|
|
511
|
+
|
|
512
|
+
| Config | Dev | Build / start |
|
|
513
|
+
|---|---|---|
|
|
514
|
+
| TypeScript (CJS or MJS) | `tsx` resolves `tsconfig.json` `paths` natively | `tsc && tsc-alias` rewrites aliases to relative paths in the compiled output |
|
|
515
|
+
| JavaScript + CJS | `module-alias` (registered as the first line of `server.js`, reading `_moduleAliases` from `package.json`) | same |
|
|
516
|
+
| JavaScript + ESM (MJS) | A generated `alias-loader.mjs`, registered via `node --experimental-loader=./alias-loader.mjs` (already wired into `npm run dev` / `npm start`) | same |
|
|
517
|
+
|
|
518
|
+
A `jsconfig.json` is also generated for JS projects so editors get alias-aware intellisense — it
|
|
519
|
+
has no effect on how the project actually runs. If you decline aliases, none of this is
|
|
520
|
+
generated and every import is a plain relative path, exactly as before this feature existed.
|
|
521
|
+
|
|
522
|
+
## Environment variables
|
|
523
|
+
|
|
524
|
+
Only variables the selected features actually need are generated, into `.env` (with working local
|
|
525
|
+
defaults) and `.env.example` (a template):
|
|
526
|
+
|
|
527
|
+
```env
|
|
528
|
+
# Application (always)
|
|
529
|
+
NODE_ENV=development
|
|
530
|
+
PORT=3000
|
|
531
|
+
CORS_ORIGIN=*
|
|
532
|
+
|
|
533
|
+
# Database (only one of these, depending on selection)
|
|
534
|
+
DATABASE_URL=
|
|
535
|
+
MONGODB_URI=
|
|
536
|
+
|
|
537
|
+
# Redis (only if Redis is needed)
|
|
538
|
+
REDIS_URL=
|
|
539
|
+
|
|
540
|
+
# Auth — JWT / refresh tokens (only if selected)
|
|
541
|
+
JWT_SECRET=
|
|
542
|
+
JWT_EXPIRES_IN=15m
|
|
543
|
+
JWT_REFRESH_SECRET=
|
|
544
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
545
|
+
|
|
546
|
+
# Auth — OAuth (only if selected; stub — see Authentication)
|
|
547
|
+
OAUTH_CLIENT_ID=
|
|
548
|
+
OAUTH_CLIENT_SECRET=
|
|
549
|
+
OAUTH_CALLBACK_URL=
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Validated at startup with Zod (`utils/validate-env.js`) — the app refuses to boot with a missing
|
|
553
|
+
or malformed required variable instead of failing confusingly later.
|
|
554
|
+
|
|
555
|
+
## Generated project commands
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
npm install
|
|
559
|
+
npm run dev # tsx watch (TS) or node --watch (JS)
|
|
560
|
+
npm run build # TypeScript only: tsc [&& tsc-alias if aliases are enabled]
|
|
561
|
+
npm start # node dist/server.js (TS) or node src/server.js (JS)
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
If PostgreSQL + Prisma was selected:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
npm run db:generate # regenerate the Prisma client
|
|
568
|
+
npm run db:migrate # run dev migrations
|
|
569
|
+
npm run db:push # push schema without a migration
|
|
570
|
+
npm run db:studio # open Prisma Studio
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Every generated project exposes `GET /health` → `{ "success": true, "data": { "status": "ok" } }`,
|
|
574
|
+
a centralized 404 handler, and a centralized error handler.
|
|
575
|
+
|
|
576
|
+
## Configuration examples
|
|
577
|
+
|
|
578
|
+
**A — Modern TypeScript API**: ESM, TypeScript, MVC, `src/`, PostgreSQL + Prisma, Redis, BullMQ,
|
|
579
|
+
Socket.IO, JWT, aliases, `entity.json`. Produces the full MVC tree shown above plus `workers/`,
|
|
580
|
+
`socket/`, `middlewares/auth.js`, and `prisma/schema.prisma` generated from your entities.
|
|
581
|
+
|
|
582
|
+
**B — Lightweight JavaScript API**: CommonJS, JavaScript, MVC, no `src/`, MongoDB + Mongoose, no
|
|
583
|
+
Redis/BullMQ/Socket.IO/auth. Produces a minimal flat tree (`controllers/`, `routes/`, `services/`,
|
|
584
|
+
`validators/`, `middlewares/`, `helpers/`, `utils/`, `configs/db/` at the project root) with only
|
|
585
|
+
`express`, `mongoose`, and the always-on security/validation dependencies.
|
|
586
|
+
|
|
587
|
+
**C — Modular backend**: ESM, TypeScript, HMVC, PostgreSQL + Prisma, aliases, `entity.json`.
|
|
588
|
+
Produces the HMVC tree shown above, one `@<model>` alias per entity, and per-module CRUD wired
|
|
589
|
+
through `src/routes/index.js`.
|
|
590
|
+
|
|
591
|
+
## Security
|
|
592
|
+
|
|
593
|
+
- Helmet, CORS (`CORS_ORIGIN`; a startup warning fires if it's still `*` in production)
|
|
594
|
+
- Zod validation on every write route, before the controller runs
|
|
595
|
+
- Rate limiting — in-memory by default, Redis-backed (`rate-limit-redis`) when selected or when
|
|
596
|
+
BullMQ is enabled; fails closed (rejects requests) rather than silently going unlimited if
|
|
597
|
+
Redis-backed and Redis is unreachable
|
|
598
|
+
- bcrypt password hashing, centralized (never duplicated into controllers)
|
|
599
|
+
- Centralized HTTP error handling — stack traces are only ever included in a `development`
|
|
600
|
+
response, never in `production`
|
|
601
|
+
- Environment validation at startup (Zod) — required variables must be present and well-formed
|
|
602
|
+
- Graceful shutdown — HTTP server → Socket.IO → BullMQ → Redis → database, in order, each awaited
|
|
603
|
+
- Automatic stripping of password/secret/hash-like fields from generated API responses
|
|
604
|
+
- Generation-time safety: a non-empty target directory is never silently overwritten, HMVC module
|
|
605
|
+
names are validated against reserved-alias collisions, and generation happens into a temporary
|
|
606
|
+
directory first, only copied into place once it fully succeeds
|
|
607
|
+
|
|
608
|
+
> GAZAN generates a backend *foundation*. Application-specific security requirements — threat
|
|
609
|
+
> modeling, auth flow correctness, secrets management, dependency audits, and so on — still need
|
|
610
|
+
> to be reviewed and implemented by the project owner.
|
|
611
|
+
|
|
612
|
+
## CLI reference
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
gazan --help # command list and usage
|
|
616
|
+
gazan --version # installed GAZAN version
|
|
617
|
+
gazan init # interactively generate a new backend project
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
There is currently no `gazan generate <resource>` (incremental regeneration into an existing
|
|
621
|
+
project) — see [Limitations](#project-status--limitations).
|
|
622
|
+
|
|
623
|
+
## Generated project lifecycle
|
|
624
|
+
|
|
625
|
+
```text
|
|
626
|
+
Install GAZAN
|
|
627
|
+
↓
|
|
628
|
+
gazan init
|
|
629
|
+
↓
|
|
630
|
+
Answer the prompts (optionally pointing at an entity.json)
|
|
631
|
+
↓
|
|
632
|
+
GAZAN normalizes your answers into one configuration object
|
|
633
|
+
↓
|
|
634
|
+
GAZAN generates the project (into a temp dir, then copies it into place on success)
|
|
635
|
+
↓
|
|
636
|
+
cd <project> && npm install
|
|
637
|
+
↓
|
|
638
|
+
Configure .env
|
|
639
|
+
↓
|
|
640
|
+
Run migrations / database setup as appropriate (e.g. npm run db:migrate)
|
|
641
|
+
↓
|
|
642
|
+
npm run dev
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
GAZAN does not run migrations, seed data, or otherwise touch a live database during generation —
|
|
646
|
+
it only writes files.
|
|
647
|
+
|
|
648
|
+
## Development
|
|
649
|
+
|
|
650
|
+
Working on GAZAN itself:
|
|
651
|
+
|
|
652
|
+
```bash
|
|
653
|
+
git clone git@github.com:korveniq/Gazan.git
|
|
654
|
+
cd Gazan
|
|
655
|
+
npm install
|
|
656
|
+
npm test
|
|
657
|
+
node bin/gazan.js init # run the generator locally against a scratch directory
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
## Testing
|
|
661
|
+
|
|
662
|
+
`npm test` runs `tests/run.js`, an end-to-end suite that generates real projects (into
|
|
663
|
+
`.test-scratch/`, gitignored) and validates them with the actual toolchain — `npm install`,
|
|
664
|
+
`tsc --noEmit`, `prisma validate`/`prisma generate`, and for a representative subset, actually
|
|
665
|
+
booting the server and checking `/health` plus the absence of module-resolution errors — not just
|
|
666
|
+
static inspection of the generated text.
|
|
667
|
+
|
|
668
|
+
The matrix covers, in combination: CJS/MJS, JS/TS, MVC/HMVC, `src`/no-`src`, PostgreSQL+Prisma,
|
|
669
|
+
MongoDB+Mongoose, MongoDB (native), no database, Redis (with and without BullMQ), BullMQ,
|
|
670
|
+
Socket.IO, each authentication method in isolation, module aliases (across every module
|
|
671
|
+
system/language combination, MVC and HMVC), and `entity.json` schemas including self-relations,
|
|
672
|
+
dual foreign keys to the same model, many-to-many, and a six-model realistic schema.
|
|
673
|
+
|
|
674
|
+
## Troubleshooting
|
|
675
|
+
|
|
676
|
+
**Node.js version** — GAZAN requires Node 18+ (see `engines` in `package.json`) and checks this
|
|
677
|
+
itself at startup, exiting with a clear message on older versions.
|
|
678
|
+
|
|
679
|
+
**Permission errors installing globally** — prefer `npx gazan-init init` over
|
|
680
|
+
`npm install -g gazan-init` if you hit `EACCES` or similar; it avoids global install
|
|
681
|
+
permissions entirely.
|
|
682
|
+
|
|
683
|
+
**`entity.json` errors** — invalid entity definitions are rejected with field-level validation
|
|
684
|
+
errors (see [entity.json](#entityjson)) before any files are generated, not partway through.
|
|
685
|
+
|
|
686
|
+
**Database connection during generation** — generation itself never requires a live database;
|
|
687
|
+
it only produces schema/config files. `npm run db:migrate` (Prisma) or connecting your generated
|
|
688
|
+
app does.
|
|
689
|
+
|
|
690
|
+
**Existing directories** — if the target directory has content in it, GAZAN asks you to cancel,
|
|
691
|
+
continue (existing files may be overwritten), or choose another directory. A directory containing
|
|
692
|
+
only a stray `.git` is still treated as empty.
|
|
693
|
+
|
|
694
|
+
## Project status / limitations
|
|
695
|
+
|
|
696
|
+
- **OAuth is a stub** (env vars + a documented `throw`, not a working provider integration).
|
|
697
|
+
- **Many-to-many CRUD writes aren't automatic** — the relation is schema-correct but generated
|
|
698
|
+
services don't read/write it (see [Relations](#relations)).
|
|
699
|
+
- **No incremental `gazan generate model`** — only full `gazan init`.
|
|
700
|
+
- **No live-database round-trip tests** in this repository's own test suite (no Postgres/Mongo/
|
|
701
|
+
Redis instance in CI/sandbox) — tests verify schema validity, compilation, and that the app
|
|
702
|
+
boots and fails at the *expected* connection boundary, not full data round-trips.
|
|
703
|
+
- **Composite (multi-field) unique constraints/indexes** aren't representable in `entity.json`'s
|
|
704
|
+
current per-field-only shape.
|
|
705
|
+
- entity.json has no per-model way to require authentication on generated routes — wire
|
|
706
|
+
`middlewares/auth.js` into a generated route file yourself if you need that.
|
|
707
|
+
|
|
708
|
+
## Contributing
|
|
709
|
+
|
|
710
|
+
Issues and pull requests are welcome. Before opening a PR:
|
|
711
|
+
|
|
712
|
+
```bash
|
|
713
|
+
npm test
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
Keep changes scoped — GAZAN's generators are composable and conditional by design; a new feature
|
|
717
|
+
should follow the same pattern (only generate what's selected, single source of truth for shared
|
|
718
|
+
config, no duplicated logic across CJS/MJS/JS/TS variants).
|
|
719
|
+
|
|
720
|
+
## License
|
|
721
|
+
|
|
722
|
+
[MIT](./LICENSE)
|