@alevnyacow/nzmt 0.15.15 → 0.15.17
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 +132 -167
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,31 @@
|
|
|
6
6
|
|
|
7
7
|
# What
|
|
8
8
|
|
|
9
|
-
Next Zod Modules Toolkit. Next.js tools you actually missed + a scaffolder for server logic & client queries. Not a framework
|
|
9
|
+
Next Zod Modules Toolkit. Next.js tools you actually missed + a scaffolder for server logic & client queries. **Not a framework.** Full-stack, batteries included.
|
|
10
|
+
|
|
11
|
+
Build full-stack features in Next.js without boilerplate. ⚡
|
|
12
|
+
|
|
13
|
+
# TL;DR
|
|
14
|
+
|
|
15
|
+
One command:
|
|
16
|
+
|
|
17
|
+
`npx nzmt crud-api user`
|
|
18
|
+
|
|
19
|
+
Gives you:
|
|
20
|
+
|
|
21
|
+
- entity and stores (Prisma and in-memory)
|
|
22
|
+
- fully typed API routes
|
|
23
|
+
- services (for Server Actions)
|
|
24
|
+
- Zod validation
|
|
25
|
+
- React Query hooks
|
|
26
|
+
|
|
27
|
+
All wired together and fully editable. No boilerplate. See `Quick start with Prisma` for a full working example.
|
|
10
28
|
|
|
11
29
|
# Why
|
|
12
30
|
|
|
13
31
|
- ☕ Keep using plain Next.js — just faster and cleaner. Skip the moment when some “helpful” framework fights you, making you wonder if coding it yourself would’ve been easier.
|
|
14
32
|
- 🧙 Focus on your domain logic without drowning in full-blown DDD.
|
|
15
|
-
- ✨ DI, Zod validation, project structure
|
|
33
|
+
- ✨ DI, Zod validation, project structure, handy API controllers and Server actions out of the box.
|
|
16
34
|
- 🪄 Services, controllers, client queries, and other programmer stuff appear at the snap of a finger — and yes, it’s fun. (Well, not *literally* at the snap of a finger — that’s just marketing, to be honest. You still need to run one CLI command.)
|
|
17
35
|
|
|
18
36
|
# Quick start with Prisma
|
|
@@ -21,226 +39,173 @@ Assuming you have a Next.js project with a generated Prisma client, and configur
|
|
|
21
39
|
|
|
22
40
|
## Setup phase
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
# 1. Install NZMT and dependencies
|
|
26
|
-
npm i inversify zod reflect-metadata @alevnyacow/nzmt
|
|
27
|
-
|
|
28
|
-
# 2. Enable decorators in tsconfig.json
|
|
29
|
-
# {
|
|
30
|
-
# "compilerOptions": {
|
|
31
|
-
# "experimentalDecorators": true,
|
|
32
|
-
# "emitDecoratorMetadata": true
|
|
33
|
-
# }
|
|
34
|
-
# }
|
|
35
|
-
|
|
36
|
-
# 3. Initialize NZMT with the absolute Prisma client path as a parameter
|
|
37
|
-
npx nzmt init prismaClientPath:@/generated/prisma/client
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
After NZMT initialization some basic infrastructure and config file were scaffolded. Open scaffolded file `/server/infrastructure/prisma/client.ts`, then import and set up there the necessary Prisma adapter. Now you’re ready to use NZMT!
|
|
41
|
-
|
|
42
|
-
## Example 1. CRUD for `User` entity with API route handlers and react queries
|
|
43
|
-
|
|
44
|
-
Assuming you have `User` prisma schema.
|
|
42
|
+
1. Install NZMT and dependencies:
|
|
45
43
|
|
|
46
44
|
```bash
|
|
47
|
-
|
|
45
|
+
npm i inversify zod reflect-metadata @alevnyacow/nzmt
|
|
48
46
|
```
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
### 1. Scaffolded `UserEntity`
|
|
48
|
+
2. Enable decorators in tsconfig.json
|
|
53
49
|
|
|
54
50
|
```ts
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export type UserModel = z.infer<typeof User.schema>
|
|
61
|
-
|
|
62
|
-
export class User {
|
|
63
|
-
static schema = z.object({
|
|
64
|
-
id: ValueObjects.Identifier.schema,
|
|
65
|
-
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
private constructor(private readonly data: UserModel) {}
|
|
69
|
-
|
|
70
|
-
static create = (data: UserModel) => {
|
|
71
|
-
const parsedModel = User.schema.parse(data)
|
|
72
|
-
return new User(parsedModel)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
get model(): UserModel {
|
|
76
|
-
return this.data
|
|
77
|
-
}
|
|
51
|
+
{
|
|
52
|
+
"compilerOptions": {
|
|
53
|
+
"experimentalDecorators": true,
|
|
54
|
+
"emitDecoratorMetadata": true
|
|
55
|
+
}
|
|
78
56
|
}
|
|
79
57
|
```
|
|
80
58
|
|
|
81
|
-
|
|
59
|
+
3. Initialize NZMT with the absolute Prisma client path as a parameter
|
|
82
60
|
|
|
83
|
-
|
|
61
|
+
```bash
|
|
62
|
+
npx nzmt init prismaClientPath:@/generated/prisma/client
|
|
63
|
+
```
|
|
84
64
|
|
|
85
|
-
|
|
65
|
+
This command generates:
|
|
86
66
|
|
|
87
|
-
|
|
88
|
-
|
|
67
|
+
- `nzmt.config.json` file
|
|
68
|
+
- DI infrastructure boilerplate
|
|
69
|
+
- Prisma Client instance injected in DI
|
|
70
|
+
- Some infrastructure helpers also already injected in DI
|
|
89
71
|
|
|
90
|
-
|
|
91
|
-
import { User } from '@/shared/entities/user'
|
|
72
|
+
4. Import and set up there the necessary Prisma adapter in `/server/infrastructure/prisma/client.ts`
|
|
92
73
|
|
|
93
|
-
|
|
94
|
-
models: {
|
|
95
|
-
list: User.schema,
|
|
96
|
-
details: User.schema,
|
|
97
|
-
},
|
|
74
|
+
## Example 1. CRUD for `User` entity with API route handlers and Server Actions
|
|
98
75
|
|
|
99
|
-
|
|
100
|
-
list: User.schema.omit({ id: true }),
|
|
101
|
-
specific: User.schema.pick({ id: true }),
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
actionsPayload: {
|
|
105
|
-
create: User.schema.omit({ id: true }),
|
|
106
|
-
update: User.schema.omit({ id: true }).partial(),
|
|
107
|
-
},
|
|
76
|
+
Assuming you have `User` prisma schema.
|
|
108
77
|
|
|
109
|
-
|
|
110
|
-
} satisfies Store.Metadata
|
|
78
|
+
1. Run NZMT scaffolder:
|
|
111
79
|
|
|
112
|
-
|
|
80
|
+
```bash
|
|
81
|
+
npx nzmt crud-api user
|
|
113
82
|
```
|
|
114
83
|
|
|
115
|
-
|
|
84
|
+
This command generates:
|
|
116
85
|
|
|
117
|
-
|
|
86
|
+
- `UserEntity`
|
|
87
|
+
- `UserStore` (with Prisma + RAM implementations)
|
|
88
|
+
- `UserService` (ready to be used in Server Actions)
|
|
89
|
+
- `UserController` proxying UserService methods
|
|
90
|
+
- User `API routes` for UserController endpoints
|
|
91
|
+
- `React Query hooks` for fetching UserController from client-side
|
|
118
92
|
|
|
119
|
-
|
|
120
|
-
- `models.details` - details model, `details` will return this entity
|
|
93
|
+
Everything is wired automatically via DI — no manual setup needed.
|
|
121
94
|
|
|
122
|
-
|
|
95
|
+
2. Describe your entity in `/shared/entities/user/user.entity.ts` (look at static `schema` field)
|
|
123
96
|
|
|
124
|
-
|
|
125
|
-
- `searchPayload.specific` - how to find one specific entity in `details` method
|
|
97
|
+
3. Tweak `UserStore` schemas if needed in `/server/stores/users/user.store.ts`
|
|
126
98
|
|
|
127
|
-
|
|
99
|
+
4. Describe how your `UserStore` contracts map to your `Prisma` client contracts in `server/stores/users/user.store.prisma.ts` (look at `mappers` object)
|
|
128
100
|
|
|
129
|
-
|
|
130
|
-
- `actionsPayload.update` - what is needed to update an entity. Note: only update payload must be here, filters will be used from `searchPayload`
|
|
101
|
+
And after one CLI command and few tweaks you can use your React query hooks or Server actions. 🪄
|
|
131
102
|
|
|
132
|
-
|
|
103
|
+
### How to use React query hooks
|
|
133
104
|
|
|
134
|
-
|
|
105
|
+
```
|
|
106
|
+
Mental model: Client → React Query → API → Controller → Service → Store → DB
|
|
107
|
+
```
|
|
135
108
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
109
|
+
Everything is already scaffolded for you, just import it and use! ✨
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
'use client'
|
|
113
|
+
|
|
114
|
+
import { useUserAPI_GET } from "@/client/shared/queries/user-controller/GET";
|
|
115
|
+
import { useUserAPI_POST } from "@/client/shared/queries/user-controller/POST";
|
|
116
|
+
|
|
117
|
+
export default function Home() {
|
|
118
|
+
const { mutate: addUser } = useUserAPI_POST()
|
|
119
|
+
const { data, isFetching } = useUserAPI_GET({ query: {} })
|
|
120
|
+
|
|
121
|
+
const addGreg = () => {
|
|
122
|
+
addUser({ body: { payload: { name: 'Greg' } } })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div>
|
|
127
|
+
<button onClick={addGreg}>
|
|
128
|
+
Add Greg
|
|
129
|
+
</button>
|
|
130
|
+
|
|
131
|
+
{isFetching ? 'Loading users...' : JSON.stringify(data)}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
153
134
|
}
|
|
154
135
|
|
|
155
|
-
@injectable()
|
|
156
|
-
export class UserPrismaStore implements UserStore {
|
|
157
|
-
..scaffolded code
|
|
158
136
|
```
|
|
159
137
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
### 3. Scaffolded `UserService` with all business methods
|
|
138
|
+
### How to use server actions
|
|
163
139
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
140
|
+
```
|
|
141
|
+
Mental model: Server Action → Service → Store → DB
|
|
142
|
+
```
|
|
167
143
|
|
|
168
|
-
|
|
144
|
+
Just get required instances from DI and use methods. That's all. ✨
|
|
169
145
|
|
|
170
|
-
|
|
146
|
+
```tsx
|
|
147
|
+
'use server'
|
|
171
148
|
|
|
172
|
-
|
|
149
|
+
import { fromDI } from "@/server/di"
|
|
150
|
+
import type { UserService } from "@/server/services/user"
|
|
173
151
|
|
|
174
|
-
|
|
152
|
+
export default async function() {
|
|
153
|
+
const userService = fromDI<UserService>('UserService')
|
|
175
154
|
|
|
176
|
-
|
|
155
|
+
const driver8 = await userService.getDetails({
|
|
156
|
+
filter: { id: 'driver-8' }
|
|
157
|
+
})
|
|
177
158
|
|
|
178
|
-
|
|
159
|
+
return <div>
|
|
160
|
+
Take a break, {JSON.stringify(driver8)}
|
|
161
|
+
{JSON.stringify(driver8)}, take a break
|
|
162
|
+
</div>
|
|
163
|
+
}
|
|
164
|
+
```
|
|
179
165
|
|
|
180
|
-
|
|
181
|
-
import { useQuery } from '@tanstack/react-query'
|
|
182
|
-
import type { UserAPI } from '@/server/controllers/user'
|
|
183
|
-
import { apiRequest } from '@/client/shared/utils'
|
|
166
|
+
# Common questions
|
|
184
167
|
|
|
185
|
-
|
|
168
|
+
## Do I really need to understand DI and other fancy concepts to use NZMT?
|
|
186
169
|
|
|
187
|
-
|
|
170
|
+
No. NZMT provides you safe and intuitive facade above `inversifyjs` and automatically registers dependencies. To get an instance you just use `fromDI` function with strongly typed keys like this:
|
|
188
171
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
queryKey: [endpoint, payload],
|
|
192
|
-
queryFn: () => apiRequest(endpoint, 'GET')(payload)
|
|
193
|
-
})
|
|
194
|
-
}
|
|
172
|
+
```tsx
|
|
173
|
+
const userService = fromDI<UserService>('UserService')
|
|
195
174
|
```
|
|
196
175
|
|
|
197
|
-
|
|
198
|
-
- `apiRequest` handles endpoint, method, and payload conveniently (also scaffolded and editable).
|
|
176
|
+
## Can I tweak scaffolded files?
|
|
199
177
|
|
|
200
|
-
|
|
178
|
+
Yes — everything is fully editable, including configuration. You can think of NZMT as shadcn-style approach for server-side logic — scaffold, then fully own the code.
|
|
201
179
|
|
|
202
|
-
|
|
180
|
+
## Why not just use plain Next.js?
|
|
203
181
|
|
|
204
|
-
|
|
182
|
+
You can.
|
|
205
183
|
|
|
206
|
-
|
|
184
|
+
NZMT removes the repetitive parts:
|
|
185
|
+
- validation
|
|
186
|
+
- API wiring
|
|
187
|
+
- client queries
|
|
188
|
+
- service layer
|
|
189
|
+
- data layer
|
|
207
190
|
|
|
208
|
-
|
|
209
|
-
npx nzmt crud-service product
|
|
210
|
-
```
|
|
191
|
+
So you can focus on your logic while NZMT handles boring tech stuff like folder structure and contracts.
|
|
211
192
|
|
|
212
|
-
|
|
193
|
+
P.S. In general, you remain within plain Next.js.
|
|
213
194
|
|
|
214
|
-
|
|
215
|
-
- tweak the Product store schemas if needed (`/server/stores/product/product.store.ts`)
|
|
216
|
-
- write Prisma store mappers (`/server/stores/product/product.store.prisma.ts`) as in previous example.
|
|
195
|
+
## Why not use Nest or tRPC?
|
|
217
196
|
|
|
218
|
-
|
|
197
|
+
Again, you can use whatever you want, God bless you.
|
|
219
198
|
|
|
220
|
-
|
|
221
|
-
'use server'
|
|
199
|
+
`NZMT` sits between `tRPC` and `NestJS`:
|
|
222
200
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
import type { ProductService } from "@/server/services/product"
|
|
201
|
+
- from tRPC — type safety and DX
|
|
202
|
+
- from NestJS — structure and layering
|
|
226
203
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
204
|
+
But:
|
|
205
|
+
- no framework lock-in
|
|
206
|
+
- no magic runtime
|
|
207
|
+
- full control over your code
|
|
208
|
+
|
|
209
|
+
Just better Next.js.
|
|
231
210
|
|
|
232
|
-
const driver8 = await userService.getDetails({
|
|
233
|
-
filter: { id: 'driver-8' }
|
|
234
|
-
})
|
|
235
|
-
const allProducts = await productService.getList({
|
|
236
|
-
filter: { }
|
|
237
|
-
})
|
|
238
211
|
|
|
239
|
-
return <div>
|
|
240
|
-
Take a break, {JSON.stringify(driver8)}
|
|
241
|
-
{JSON.stringify(driver8)}, take a break
|
|
242
|
-
|
|
243
|
-
Also we've got some products: {JSON.stringify(allProducts)}
|
|
244
|
-
</div>
|
|
245
|
-
}
|
|
246
|
-
```
|