chadstart 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.
Files changed (115) hide show
  1. package/.dockerignore +10 -0
  2. package/.env.example +46 -0
  3. package/.github/workflows/browser-test.yml +34 -0
  4. package/.github/workflows/docker-publish.yml +54 -0
  5. package/.github/workflows/docs.yml +31 -0
  6. package/.github/workflows/npm-chadstart.yml +27 -0
  7. package/.github/workflows/npm-sdk.yml +38 -0
  8. package/.github/workflows/test.yml +85 -0
  9. package/.weblate +9 -0
  10. package/Dockerfile +23 -0
  11. package/README.md +348 -0
  12. package/admin/index.html +2802 -0
  13. package/admin/login.html +207 -0
  14. package/chadstart.example.yml +416 -0
  15. package/chadstart.schema.json +367 -0
  16. package/chadstart.yaml +53 -0
  17. package/cli/cli.js +295 -0
  18. package/core/api-generator.js +606 -0
  19. package/core/auth.js +298 -0
  20. package/core/db.js +384 -0
  21. package/core/entity-engine.js +166 -0
  22. package/core/error-reporter.js +132 -0
  23. package/core/file-storage.js +97 -0
  24. package/core/functions-engine.js +353 -0
  25. package/core/openapi.js +171 -0
  26. package/core/plugin-loader.js +92 -0
  27. package/core/realtime.js +93 -0
  28. package/core/schema-validator.js +50 -0
  29. package/core/seeder.js +231 -0
  30. package/core/telemetry.js +119 -0
  31. package/core/upload.js +372 -0
  32. package/core/workers/php_worker.php +19 -0
  33. package/core/workers/python_worker.py +33 -0
  34. package/core/workers/ruby_worker.rb +21 -0
  35. package/core/yaml-loader.js +64 -0
  36. package/demo/chadstart.yaml +178 -0
  37. package/demo/docker-compose.yml +31 -0
  38. package/demo/functions/greet.go +39 -0
  39. package/demo/functions/hello.cpp +18 -0
  40. package/demo/functions/hello.py +13 -0
  41. package/demo/functions/hello.rb +10 -0
  42. package/demo/functions/onTodoCreated.js +13 -0
  43. package/demo/functions/ping.sh +13 -0
  44. package/demo/functions/stats.js +22 -0
  45. package/demo/public/index.html +522 -0
  46. package/docker-compose.yml +17 -0
  47. package/docs/access-policies.md +155 -0
  48. package/docs/admin-ui.md +29 -0
  49. package/docs/angular.md +69 -0
  50. package/docs/astro.md +71 -0
  51. package/docs/auth.md +160 -0
  52. package/docs/cli.md +56 -0
  53. package/docs/config.md +127 -0
  54. package/docs/crud.md +627 -0
  55. package/docs/deploy.md +113 -0
  56. package/docs/docker.md +59 -0
  57. package/docs/entities.md +385 -0
  58. package/docs/functions.md +196 -0
  59. package/docs/getting-started.md +79 -0
  60. package/docs/groups.md +85 -0
  61. package/docs/index.md +5 -0
  62. package/docs/llm-rules.md +81 -0
  63. package/docs/middlewares.md +78 -0
  64. package/docs/overrides/home.html +350 -0
  65. package/docs/plugins.md +59 -0
  66. package/docs/react.md +75 -0
  67. package/docs/realtime.md +43 -0
  68. package/docs/s3-storage.md +40 -0
  69. package/docs/security.md +23 -0
  70. package/docs/stylesheets/extra.css +375 -0
  71. package/docs/svelte.md +71 -0
  72. package/docs/telemetry.md +97 -0
  73. package/docs/upload.md +168 -0
  74. package/docs/validation.md +115 -0
  75. package/docs/vue.md +86 -0
  76. package/docs/webhooks.md +87 -0
  77. package/index.js +11 -0
  78. package/locales/en/admin.json +169 -0
  79. package/mkdocs.yml +82 -0
  80. package/package.json +65 -0
  81. package/playwright.config.js +24 -0
  82. package/public/.gitkeep +0 -0
  83. package/sdk/README.md +284 -0
  84. package/sdk/package.json +39 -0
  85. package/sdk/scripts/build.js +58 -0
  86. package/sdk/src/index.js +368 -0
  87. package/sdk/test/sdk.test.cjs +340 -0
  88. package/sdk/types/index.d.ts +217 -0
  89. package/server/express-server.js +734 -0
  90. package/test/access-policies.test.js +96 -0
  91. package/test/ai.test.js +81 -0
  92. package/test/api-keys.test.js +361 -0
  93. package/test/auth.test.js +122 -0
  94. package/test/browser/admin-ui.spec.js +127 -0
  95. package/test/browser/global-setup.js +71 -0
  96. package/test/browser/global-teardown.js +11 -0
  97. package/test/db.test.js +227 -0
  98. package/test/entity-engine.test.js +193 -0
  99. package/test/error-reporter.test.js +140 -0
  100. package/test/functions-engine.test.js +240 -0
  101. package/test/groups.test.js +212 -0
  102. package/test/hot-reload.test.js +153 -0
  103. package/test/i18n.test.js +173 -0
  104. package/test/middleware.test.js +76 -0
  105. package/test/openapi.test.js +67 -0
  106. package/test/schema-validator.test.js +83 -0
  107. package/test/sdk.test.js +90 -0
  108. package/test/seeder.test.js +279 -0
  109. package/test/settings.test.js +109 -0
  110. package/test/telemetry.test.js +254 -0
  111. package/test/test.js +17 -0
  112. package/test/upload.test.js +265 -0
  113. package/test/validation.test.js +96 -0
  114. package/test/yaml-loader.test.js +93 -0
  115. package/utils/logger.js +24 -0
package/docs/crud.md ADDED
@@ -0,0 +1,627 @@
1
+ ---
2
+ id: crud
3
+ title: CRUD Operations
4
+ description: Documentation for out-of-the-box CRUD endpoints with ChadStart. Paginated lists, detail views, creating and updating single or collection entities.
5
+ ---
6
+
7
+ # CRUD operations
8
+
9
+ ## Introduction
10
+
11
+ Once you created your [entities](./entities.md), you probably want to interact with them. It is easy to connect your client to your ChadStart backend.
12
+
13
+ ChadStart provides out-of-the-box CRUD endpoints through the **REST API** or the **JS SDK**.
14
+
15
+ !!! info
16
+ By default CRUD endpoints are private, only accessible for logged-in **admin** users. You can open them to the public using [policies](./access-policies.md).
17
+
18
+ ## Using the REST API
19
+
20
+ ChadStart exposes a REST API for CRUD operations. The **OpenAPI** documentation is automatically generated and the UI is available at http://localhost:3000/api. Have a look!
21
+
22
+ An`openapi.yml` file is also generated along a `types.ts` file in the `./chadstart` folder. Those 2 files are an amazing source of context for your **LLM**. If you want to connect a frontend to your ChadStart backend, make sure that your **AI coding tool** sees those files to simplify your development.
23
+
24
+ For CRUD endpoints, this prefix is followed by `collections` for [collections entities](#collections) and `singles` for [single entities](#singles) and by the slug of your entity (you can change it in the [entity params](entities.md#entity-params))
25
+
26
+ Examples:
27
+
28
+ - `http://localhost:3000/api/collections/cats` gets the list of the cats
29
+ - `http://localhost:3000/api/singles/home-content` gets the home content
30
+
31
+ !!! tip
32
+ In addition to **CRUD endpoints** that are generated automatically, you also can create your own [custom endpoints](./functions.md) to add your custom logic.
33
+
34
+ ## Using the JavaScript SDK
35
+
36
+ The **ChadStart JS SDK** is used to fetch and manipulate your data from your JS client using an elegant and human-friendly interface.
37
+
38
+ The SDK can be integrated in any frontend stack app like [React](./react.md), [Vue](./vue.md), [Svelte](./svelte.md), [Astro](./astro.md), [Angular](./angular.md).... Or even by another server using NodeJS!
39
+
40
+ Install it via the terminal:
41
+
42
+ ```bash
43
+ npm i @chadstart/sdk
44
+ ```
45
+
46
+ Use the SDK directly in your favorite frontend:
47
+
48
+ ```js title="Example SDK usage"
49
+ import ChadStart from '@chadstart/sdk'
50
+
51
+ // Initialize client with default backend URL: http://localhost:3000.
52
+ const chadstart = new ChadStart()
53
+
54
+ // Initialize client with custom base URL.
55
+ const chadstart = new ChadStart('https://example.com')
56
+
57
+ // Perform CRUD operations...
58
+ const cats = await chadstart.from('cats').find()
59
+ ```
60
+
61
+ ## Collections
62
+
63
+ The following CRUD operations can be done on [collections](./entities.md#collections) entities. A collection is a list of items that share a similar schema.
64
+
65
+ ### Get a list of items
66
+
67
+ This operation will fetch a list of items from a collection.
68
+ === "REST API"
69
+ **Request URL**: `GET /api/collections/:slug`
70
+
71
+ ```http title="Example HTTP Request"
72
+ GET /api/collection/users
73
+ ```
74
+
75
+ ```json title="Example HTTP Response"
76
+ {
77
+ "data": [
78
+ {
79
+ "id": "2c4e6a8b-0d1f-4357-9ace-bdf024681357",
80
+ "name": "Lara"
81
+ },
82
+ {
83
+ "id": "e4d5c6b7-a890-4123-9876-543210fedcba",
84
+ "name": "Karl"
85
+ }
86
+ ],
87
+ "currentPage": 1,
88
+ "lastPage": 1,
89
+ "from": 1,
90
+ "to": 10,
91
+ "total": 3,
92
+ "perPage": 10
93
+ }
94
+ ```
95
+
96
+ **List filters**
97
+
98
+ You can filter by [property](./entities.md#properties) to refine the list of items. Use suffix to pass logic to it:
99
+
100
+ | Suffix | Description | Example |
101
+ | ---------- | --------------------- | -------------------------- |
102
+ | **\_eq** | equals | `isActive_eq=true` |
103
+ | **\_neq** | not equals | `name_neq=alice` |
104
+ | **\_gt** | greater than | `birthdate_gt=2020-01-01 ` |
105
+ | **\_gte** | greater than or equal | `age_gte=4` |
106
+ | **\_lt** | less than | `amount_lt=400` |
107
+ | **\_lte** | less than or equal | `amount_lte=400` |
108
+ | **\_like** | like | `name_like=%bi%` |
109
+ | **\_in** | included in | `customer_in=1,2,3` |
110
+
111
+ **Pagination**
112
+
113
+ All list requests are paginated by default. Just use the `page` parameter to choose your page and the `perPage` param if you want to change the number of items per page.
114
+
115
+ | Param | Description | Example |
116
+ | ----------- | -------------------------------- | ------------ |
117
+ | **page** | The number of the page requested | `page=3` |
118
+ | **perPage** | The number of items of each page | `perPage=40` |
119
+
120
+ **Order**
121
+
122
+ Order your list by a defined property. By default the results are ordered by `id` in a `DESC` order and thus shows the new ones first.
123
+
124
+ | Param | Description | Example |
125
+ | ----------- | ------------------------------------------ | ------------- |
126
+ | **orderBy** | The name of property you want to order by. | `orderBy=age` |
127
+ | **order** | Ascending 'ASC' or Descending 'DESC' | `order=DESC` |
128
+
129
+ === "JS SDK"
130
+ ```js title="Example SDK usage"
131
+ // Get all users.
132
+ const users = await chadstart.from('users').find()
133
+
134
+ console.log(users);
135
+ // Output: {
136
+ // "data": [
137
+ // {
138
+ // "id": 'e4d5c6b7-a890-4123-9876-543210fedcba',
139
+ // "name": "Lara"
140
+ // },
141
+ // {
142
+ // "id": '2c4e6a8b-0d1f-4357-9ace-bdf024681357',
143
+ // "name": "Karl"
144
+ // }
145
+ // ],
146
+ // "currentPage": 1,
147
+ // "lastPage": 1,
148
+ // "from": 1,
149
+ // "to": 10,
150
+ // "total": 3,
151
+ // "perPage": 10
152
+ // }
153
+ ```
154
+
155
+ **List filters**
156
+
157
+ You can filter by [property](./entities.md#properties) to refine the list of items. Use the `where()` function with the correct operator to do it.
158
+
159
+ ```js title="Example SDK list filtering"
160
+ const cats = await chadstart
161
+ .from('cats')
162
+ .where('breed = siamese')
163
+ .andWhere('active = true')
164
+ .andWhere('birthDate > 2020-01-01')
165
+ .find()
166
+ ```
167
+
168
+ **Filter operators**
169
+
170
+ | Operator | Description | Example |
171
+ | -------- | --------------------- | ---------------------------------- |
172
+ | **=** | equals | `.where('isActive = true')` |
173
+ | **!=** | not equals | `.where('name != alice')` |
174
+ | **>** | greater than | `.where('birthdate > 2020-01-01')` |
175
+ | **>=** | greater than or equal | `.where('age >= 4')` |
176
+ | **\<** | less than | `.where('amount < 400')` |
177
+ | **\<=** | less than or equal | `.where('amount <= 400')` |
178
+ | **like** | like | `.where('name_like=%bi%')` |
179
+ | **in** | included in | `.where('customer_in=1,2,3')` |
180
+
181
+ **Pagination**
182
+
183
+ All list requests are paginated by default. Just use the `page` parameter to chose your page and the `perPage` param if you want to change the number of items per page.
184
+
185
+ ```js title="Example SDK list pagination"
186
+ const cats = await chadstart.from('cats').find({ page: 1, perPage: 10 })
187
+ ```
188
+
189
+ **Order**
190
+
191
+ Order your list by a defined property. By default the results are ordered by `id` in a `DESC` order and thus shows the new ones first.
192
+
193
+ ```js title="Example SDK order"
194
+ // Order cats.
195
+ const cats = await chadstart.from('cats').orderBy('age', { desc: true }).find()
196
+ ```
197
+
198
+ ### Get a single item
199
+
200
+ This operation will fetch a single item based on its ID.
201
+
202
+ === "REST API"
203
+ **Request URL**: `GET /api/collections/:slug/:id`
204
+
205
+ ```http title="Example HTTP Request"
206
+ GET /api/collections/cats/2c4e6a8b-0d1f-4357-9ace-bdf024681357
207
+ ```
208
+
209
+ ```json title="Example HTTP Response"
210
+ {
211
+ "name": "Mina",
212
+ "description": "A really cute cat"
213
+ }
214
+ ```
215
+
216
+ === "JS SDK"
217
+ ```js title="Example SDK usage"
218
+ // Get cat with ID 2c4e6a8b-0d1f-4357-9ace-bdf024681357
219
+ const cat = await chadstart.from('cats').findOneById('2c4e6a8b-0d1f-4357-9ace-bdf024681357')
220
+
221
+ console.log(cat);
222
+ // Output: {
223
+ // name: "Mina",
224
+ // description: "A really cute cat"
225
+ // }
226
+ ```
227
+
228
+ ### Create a new item
229
+
230
+ This operation will create a new item and store it in the database. The newly created item is returned as response.
231
+ === "REST API"
232
+ **Request URL**: `POST /api/collections/:slug`
233
+
234
+ ```http title="Example HTTP Request"
235
+ POST /api/collections/pokemons
236
+ Content-Type: application/json
237
+ Body:
238
+ {
239
+ "name": "Pikachu",
240
+ "type": "electric",
241
+ "level": 3,
242
+ }
243
+
244
+ ```
245
+
246
+ ```json title="Example HTTP Response"
247
+ {
248
+ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
249
+ "name": "Pikachu",
250
+ "type": "electric",
251
+ "level": 3,
252
+ }
253
+ ```
254
+
255
+ === "JS SDK"
256
+ ```js title="Example SDK usage"
257
+ // Create a new item in the "pokemons" entity.
258
+ const newPokemon = await chadstart.from('pokemons').create({
259
+ name: "Pikachu",
260
+ type: "electric",
261
+ level: 3,
262
+ })
263
+
264
+ console.log(newPokemon);
265
+ // Output: {
266
+ // id: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
267
+ // name: "Pikachu",
268
+ // type: "electric",
269
+ // level: 3
270
+ // }
271
+ ```
272
+
273
+ ### Update an item
274
+
275
+ This operation will replace an existing item by the payload provided in the request and returns the updated item.
276
+
277
+ Unlike [partial updates](#patch-an-item), this operation will replace the whole item by the new one. Missing or empty properties will delete the previous ones.
278
+
279
+ === "REST API"
280
+ **Request URL**: `PUT /api/collections/:slug/:id`
281
+
282
+ ```http title="Example HTTP Request"
283
+ PUT /api/collections/pokemons/6ba7b810-9dad-11d1-80b4-00c04fd430c8
284
+ Content-Type: application/json
285
+ Body:
286
+ {
287
+ "name": "Raichu",
288
+ "type": "electric",
289
+ "level": 8
290
+ }
291
+
292
+ ```
293
+
294
+ ```json title="Example HTTP Response"
295
+ {
296
+ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
297
+ "name": "Raichu",
298
+ "type": "electric",
299
+ "level": 8
300
+ }
301
+ ```
302
+
303
+ === "JS SDK"
304
+ ```js title="Example SDK usage"
305
+ // Updates the Pokemon item with ID a1b2c3d4-e5f6-4789-abcd-ef0123456789.
306
+ const newPokemon = await chadstart.from('pokemons').update('a1b2c3d4-e5f6-4789-abcd-ef0123456789', {
307
+ name: "Raichu",
308
+ type: "electric",
309
+ level: 8,
310
+ })
311
+
312
+ console.log(newPokemon);
313
+ // Output: {
314
+ // id: "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
315
+ // name: "Raichu",
316
+ // type: "electric",
317
+ // level: 8
318
+ // }
319
+ ```
320
+
321
+ ### Patch an item
322
+
323
+ This operation will partially replace an existing item and return the updated item.
324
+
325
+ Unlike [fully replacement](#update-an-item), this operation will only modify the properties provided in the payload and leave the other ones as they are.
326
+ === "REST API"
327
+ **Request URL**: `PATCH /api/collections/:slug/:id`
328
+
329
+ ```http title="Example HTTP Request"
330
+ PATCH /api/collections/pokemons/a1b2c3d4-e5f6-4789-abcd-ef0123456789
331
+ Content-Type: application/json
332
+ Body:
333
+ {
334
+ "level": 5,
335
+ }
336
+ ```
337
+
338
+ ```json title="Example HTTP Response"
339
+ {
340
+ "id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
341
+ "name": "Pikachu",
342
+ "type": "electric",
343
+ "level": 5
344
+ }
345
+ ```
346
+
347
+ === "JS SDK"
348
+ ```js title="Example SDK usage"
349
+ // Patches the Pokemon item with ID a1b2c3d4-e5f6-4789-abcd-ef0123456789.
350
+ const newPokemon = await chadstart.from('pokemons').patch('a1b2c3d4-e5f6-4789-abcd-ef0123456789', {
351
+ level: 5
352
+ })
353
+
354
+ console.log(newPokemon);
355
+ // Output: {
356
+ // id: "a1b2c3d4-e5f6-4789-abcd-ef0123456789",
357
+ // name: "Pikachu",
358
+ // type: "electric",
359
+ // level: 5
360
+ }
361
+ ```
362
+
363
+ ### Delete an item
364
+
365
+ This operation will delete permanently an item from the database. This is an irreversible action. The deleted item is returned in the response.
366
+
367
+ === "REST API"
368
+ **Request URL**: `DELETE /api/collections/:slug/:id`
369
+
370
+ ```http title="Example HTTP Request"
371
+ DELETE api/collections/cats/550e8400-e29b-41d4-a716-446655440000
372
+
373
+ ```
374
+
375
+ ```json title="Example HTTP Response"
376
+ {
377
+ "name": "Fido",
378
+ "description": "A cute black cat"
379
+ }
380
+ ```
381
+
382
+ === "JS SDK"
383
+ ```js title="Example SDK usage"
384
+ // Delete the cat with ID 550e8400-e29b-41d4-a716-446655440000
385
+ const deletedCat = await chadstart.from('cats').delete('550e8400-e29b-41d4-a716-446655440000')
386
+
387
+ console.log(deletedCat);
388
+ // Output: {
389
+ // name: "Fido",
390
+ // description: "A cute black cat"
391
+ // }
392
+ ```
393
+
394
+ ## Singles
395
+
396
+ The following operations can be done on [singles](./entities.md#singles) entities. As there is only a single item per entity, there is no list-related operations, and single items cannot be deleted as we always want to return an object, even if empty.
397
+
398
+ ### Get a single item
399
+
400
+ === "REST API"
401
+ **Request URL**: `GET /api/singles/:slug`
402
+
403
+ ```http title="Example HTTP Request"
404
+ GET /api/singles/homepage
405
+ ```
406
+
407
+ ```json title="Example HTTP Response"
408
+ {
409
+ "title": "My title",
410
+ "description": "Welcome to my website!"
411
+ }
412
+ ```
413
+
414
+ === "JS SDK"
415
+ ```js title="Example SDK usage"
416
+ // Get the homepage entity.
417
+ const homepage = await chadstart.single('homepage').get()
418
+
419
+ console.log(homepage);
420
+ // Output: {
421
+ // title: "My title",
422
+ // description: "Welcome to my website!"
423
+ // }
424
+ ```
425
+
426
+ ### Update an item
427
+
428
+ This operation will replace an existing item by the payload provided in the request. Unlike [partial updates](#patch-an-item_1), this operation will replace the whole item by the new one. Missing or empty properties will delete the previous ones.
429
+ === "REST API"
430
+ **Request URL**: `PUT /api/singles/:slug`
431
+
432
+ ```http title="Example HTTP Request"
433
+ PUT /api/singles/homepage
434
+ Content-Type: application/json
435
+ Body:
436
+ {
437
+ "title": "My new title",
438
+ "description": "My new description"
439
+ }
440
+
441
+ ```
442
+
443
+ ```json title="Example HTTP Response"
444
+ {
445
+ "title": "My new title",
446
+ "description": "My new description"
447
+ }
448
+ ```
449
+
450
+ === "JS SDK"
451
+ ```js title="Example SDK usage"
452
+ // Update single entity.
453
+ const newHomepage = await chadstart.single('homepage').update({
454
+ title: 'My new title',
455
+ description: 'My new description'
456
+ })
457
+
458
+ console.log(newHomepage);
459
+ // Output: {
460
+ // title: "My new title",
461
+ // description: "My new description"
462
+ // }
463
+ ```
464
+
465
+ ### Patch an item
466
+
467
+ This operation will partially replace an existing item. Unlike [fully replacement](#update-an-item_1), this operation will only modify the properties provided in the payload and leave the other ones as they are.
468
+
469
+ === "REST API"
470
+ **Request URL**: `PATCH /api/singles/:slug`
471
+
472
+ ```http title="Example HTTP Request"
473
+ PATCH /api/singles/homepage
474
+ Content-Type: application/json
475
+ Body:
476
+ {
477
+ "title": "My new title"
478
+ }
479
+ ```
480
+
481
+ ```json title="Example HTTP Response"
482
+ {
483
+ "title": "My new title",
484
+ "description": "Welcome to my website!"
485
+ }
486
+ ```
487
+
488
+ === "JS SDK"
489
+ ```js title="Example SDK usage"
490
+ // Update single entity partially.
491
+ const homepage = await chadstart.single('homepage').patch({
492
+ title: 'My new title'
493
+ })
494
+ console.log(homepage)
495
+ // Output: {
496
+ // title: "My new title",
497
+ // description: "Welcome to my website!"
498
+ // }
499
+ ```
500
+
501
+ ## Work with relations
502
+
503
+ If you added [relationships](./entities.md#relations) between your entities, you probably want to include them in the CRUD operations.
504
+
505
+ ### Load relations
506
+
507
+ You can specify which relations you want to load with your entities in your query. **Eager relations** are loaded automatically.
508
+
509
+ === "JS SDK"
510
+ ```js
511
+ // Fetch entities with 2 relations.
512
+ const cities = await chadstart
513
+ .from('cities')
514
+ .with(['region', 'mayor'])
515
+ .find()
516
+
517
+ // Fetch nested relations.
518
+ const cities = await chadstart
519
+ .from('cities')
520
+ .with(['region', 'region.country', 'region.country.planet'])
521
+ .find()
522
+ ```
523
+
524
+ === "REST API"
525
+ ```http
526
+
527
+ // Fetch entities with 2 relations.
528
+ GET http://localhost:3000/api/dynamic/city?relations=region,mayor
529
+
530
+ // Fetch nested relations.
531
+ GET http://localhost:3000/api/dynamic/city?relations=region,region.country,region.country.planet
532
+ ```
533
+
534
+ ### Filter by relation
535
+
536
+ Once the relation is loaded, you can also filter items by their relation id or properties.
537
+
538
+ === "JS SDK"
539
+ ```js
540
+ // Get all cats that belong to owner with id 3f2504e0-4f89-11d3-9a0c-0305e82c3301.
541
+ const cats = await chadstart
542
+ .from('cats')
543
+ .with(['owner'])
544
+ .where('owner.id = 3f2504e0-4f89-11d3-9a0c-0305e82c3301')
545
+ .find()
546
+
547
+ // Get all cats that have an owner with name "Jorge".
548
+ const cats = await chadstart
549
+ .from('cats')
550
+ .with(['owner'])
551
+ .where('owner.name = Jorge')
552
+ .find()
553
+ ```
554
+
555
+ === "REST API"
556
+ ```http
557
+ // Get all cats that belong to owner with id 3f2504e0-4f89-11d3-9a0c-0305e82c3301.
558
+ GET http://localhost:3000/api/dynamic/cats?relations=owner&owner.id_eq=3f2504e0-4f89-11d3-9a0c-0305e82c3301
559
+
560
+ // Get all cats that have an owner with name "Jorge".
561
+ GET http://localhost:3000/api/dynamic/cats?relations=owner&owner.name_eq=Jorge
562
+ ```
563
+
564
+ ### Store relations
565
+
566
+ To store or update an item with its relations, you have to pass the relation id(s) as a property that end with **Id** for many-to-one and **Ids** for many-to-many like `companyId` or `tagIds`
567
+
568
+ === "REST API"
569
+ ```http
570
+ // Store a new player with relations Team and Skill.
571
+ POST http://localhost:3000/api/dynamic/players
572
+ Content-Type: application/json
573
+ {
574
+ "name": "Mike",
575
+ "teamId": 'e4d5c6b7-a890-4123-9876-543210fedcba',
576
+ "skillIds": ['12345678-1234-5678-9abc-123456789012', '3f2504e0-4f89-11d3-9a0c-0305e82c3301']
577
+ }
578
+ ```
579
+
580
+ === "JS SDK"
581
+ ```js
582
+ // Store a new player with relations Team and Skill.
583
+ const newPlayer = await chadstart.from('players').create({
584
+ name: 'Mike',
585
+ teamId: 'e4d5c6b7-a890-4123-9876-543210fedcba',
586
+ skillIds: ['12345678-1234-5678-9abc-123456789012', '3f2504e0-4f89-11d3-9a0c-0305e82c3301']
587
+ })
588
+ ```
589
+
590
+ !!! note
591
+ When storing **many-to-many** relations, you always need to **pass an array**, even if you just have one single value.
592
+
593
+ ### Update relations
594
+
595
+ As for updating properties, you can either do a **full replacement** using the update function (PUT) or a **partial replacement** using the patch function (PATCH).
596
+
597
+ === "REST API"
598
+ ```http
599
+ // Replaces the whole skill relations by the new skillIds array.
600
+ PUT http://localhost:3000/api/dynamic/players/e4d5c6b7-a890-4123-9876-543210fedcba
601
+ Content-Type: application/json
602
+ {
603
+ name: 'Mike',
604
+ teamId: 'e4d5c6b7-a890-4123-9876-543210fedcba',
605
+ skillIds: ['12345678-1234-5678-9abc-123456789012', '3f2504e0-4f89-11d3-9a0c-0305e82c3301']
606
+ }
607
+
608
+ // Updates the team without changing the skills or the name.
609
+ PATCH http://localhost:3000/api/dynamic/players/e4d5c6b7-a890-4123-9876-543210fedcba
610
+ Content-Type: application/json
611
+ {
612
+ teamId: '9b2fff23-ec93-4b48-9322-bbd4b6b5b123',
613
+ }
614
+ ```
615
+
616
+ === "JS SDK"
617
+ ```js
618
+ // Replaces the whole skill relations by the new skillIds array.
619
+ await chadstart.from('players').update('e4d5c6b7-a890-4123-9876-543210fedcba', {
620
+ name: 'Mike',
621
+ teamId: 'e4d5c6b7-a890-4123-9876-543210fedcba',
622
+ skillIds: ['12345678-1234-5678-9abc-123456789012', '3f2504e0-4f89-11d3-9a0c-0305e82c3301']
623
+ })
624
+
625
+ // Updates the team without changing the skills or the name.
626
+ await chadstart.from('players').patch('e4d5c6b7-a890-4123-9876-543210fedcba', {teamId: '9b2fff23-ec93-4b48-9322-bbd4b6b5b123'})
627
+ ```