json-rest-api 1.0.7 → 1.0.8

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 (146) hide show
  1. package/.claude/agents/library-guide-expert.md +29 -0
  2. package/.claude/agents/rest-api-internals-expert.md +53 -0
  3. package/CLAUDE.md +139 -1
  4. package/README.md +2 -5
  5. package/docTest.js +238 -0
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +955 -0
  7. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +54 -0
  8. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +750 -0
  9. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +494 -0
  10. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +398 -0
  11. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +882 -0
  12. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +709 -0
  13. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +1233 -0
  14. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +796 -0
  15. package/{README_X_Relationship_Plugin.md → docs/GUIDE/GUIDE_2_9_Relationships_Urls.md} +1 -4
  16. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +66 -0
  17. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +1246 -0
  18. package/docs/GUIDE/GUIDE_5_Non-Db_Stores.md +0 -0
  19. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1490 -0
  20. package/{README_X_SocketIO.md → docs/GUIDE/GUIDE_X_SocketIO.md} +17 -17
  21. package/docs/GUIDE/index.md +58 -0
  22. package/docs/GUIDE.md +61 -2238
  23. package/docs/ONBOARDING/Computed_Properties_And_Sparse_Fields.md +398 -0
  24. package/docs/README.md +57 -2239
  25. package/{README_2_Computed_and_hidden_fields.md → docs/README_VARIOUS/GUIDE_2_Computed_and_hidden_fields.md} +22 -17
  26. package/docs/README_VARIOUS/GUIDE_IN_DOCS.md +2277 -0
  27. package/{README.ORIG.md → docs/README_VARIOUS/README.ORIG.md} +8 -8
  28. package/{README_8_Polymorphic_Relationships.md → docs/README_VARIOUS/README_8_Polymorphic_Relationships.md} +1 -1
  29. package/docs/README_VARIOUS/README_IN_DOCS.ms +2277 -0
  30. package/{RELATIONSHIPS_ADVANCED.md → docs/README_VARIOUS/RELATIONSHIPS_ADVANCED.md} +3 -3
  31. package/{correct-schema-format.md → docs/README_VARIOUS/correct-schema-format.md} +13 -13
  32. package/{pagination.md → docs/README_VARIOUS/pagination.md} +11 -8
  33. package/docs/assets/css/style.scss +78 -5
  34. package/docs/index.md +2 -12
  35. package/helper-reorganization-plan.md +85 -0
  36. package/helper-usage-analysis.md +94 -0
  37. package/package.json +5 -4
  38. package/plugins/core/bulk-operations-plugin.js +2 -2
  39. package/plugins/core/connectors/express-plugin.js +90 -32
  40. package/plugins/core/lib/querying/knex-cross-table-search.js +609 -0
  41. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +491 -0
  42. package/plugins/core/lib/querying/knex-pagination-helpers.js +544 -0
  43. package/plugins/core/lib/{knex-process-includes.js → querying/knex-process-includes.js} +65 -50
  44. package/plugins/core/lib/querying/knex-query-helpers-base.js +74 -0
  45. package/plugins/core/lib/{knex-query-helpers.js → querying/knex-query-helpers.js} +416 -180
  46. package/plugins/core/lib/{knex-relationship-includes.js → querying/knex-relationship-includes.js} +430 -114
  47. package/plugins/core/lib/querying/knex-window-queries.js +328 -0
  48. package/plugins/core/lib/querying-writing/compile-schemas.js +328 -0
  49. package/plugins/core/{utils → lib/querying-writing}/connectors-query-parser.js +62 -57
  50. package/plugins/core/lib/{database-capabilities.js → querying-writing/database-capabilities.js} +113 -7
  51. package/plugins/core/lib/querying-writing/database-value-normalizers.js +369 -0
  52. package/plugins/core/lib/querying-writing/default-data-helpers.js +72 -0
  53. package/plugins/core/{utils → lib/querying-writing}/field-utils.js +41 -31
  54. package/plugins/core/{utils → lib/querying-writing}/knex-constants.js +1 -0
  55. package/plugins/core/lib/{knex-field-helpers.js → querying-writing/knex-field-helpers.js} +190 -57
  56. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +162 -0
  57. package/plugins/core/lib/{payload-validators.js → querying-writing/payload-validators.js} +297 -217
  58. package/plugins/core/lib/querying-writing/schema-helpers.js +497 -0
  59. package/plugins/core/lib/querying-writing/scope-validations.js +431 -0
  60. package/plugins/core/lib/{simplified-helpers.js → querying-writing/simplified-helpers.js} +216 -176
  61. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +110 -0
  62. package/plugins/core/lib/writing/many-to-many-manipulations.js +300 -0
  63. package/plugins/core/lib/writing/relationship-processor.js +252 -0
  64. package/plugins/core/rest-api-knex-plugin.js +183 -62
  65. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +7 -0
  66. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +72 -0
  67. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +102 -0
  68. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +50 -0
  69. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +39 -0
  70. package/plugins/core/rest-api-plugin-methods/add-route.js +23 -0
  71. package/plugins/core/rest-api-plugin-methods/check-permissions.js +18 -0
  72. package/plugins/core/rest-api-plugin-methods/common.js +605 -0
  73. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +115 -0
  74. package/plugins/core/rest-api-plugin-methods/delete.js +109 -0
  75. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +186 -0
  76. package/plugins/core/rest-api-plugin-methods/get-related.js +295 -0
  77. package/plugins/core/rest-api-plugin-methods/get-relationship.js +62 -0
  78. package/plugins/core/rest-api-plugin-methods/get.js +187 -0
  79. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +56 -0
  80. package/plugins/core/rest-api-plugin-methods/patch.js +193 -0
  81. package/plugins/core/rest-api-plugin-methods/post-relationship.js +104 -0
  82. package/plugins/core/rest-api-plugin-methods/post.js +157 -0
  83. package/plugins/core/rest-api-plugin-methods/put.js +257 -0
  84. package/plugins/core/rest-api-plugin-methods/query.js +208 -0
  85. package/plugins/core/rest-api-plugin.js +108 -2498
  86. package/plugins/core/socketio-plugin.js +28 -5
  87. package/plugins/storage/local-storage.js +2 -2
  88. package/tests/computed-fields.test.js +483 -0
  89. package/tests/cors.test.js +4 -4
  90. package/tests/curl.test.js +5 -4
  91. package/tests/custom-idproperty-relationships.test.js +891 -0
  92. package/tests/field-getters.test.js +353 -0
  93. package/tests/field-setters.test.js +310 -0
  94. package/tests/fixtures/api-configs.js +800 -28
  95. package/tests/jwt-auth.test.js +0 -2
  96. package/tests/nested-includes.test.js +1 -1
  97. package/tests/pagination-cursor-multifield.test.js +514 -0
  98. package/tests/pagination-enhanced.test.js +12 -9
  99. package/tests/polymorphic-relationships.test.js +10 -8
  100. package/tests/polymorphic-simple.test.js +6 -4
  101. package/tests/positioning-advanced.test.js +12 -12
  102. package/tests/positioning-extended.test.js +25 -25
  103. package/tests/positioning-multifilter.test.js +24 -24
  104. package/tests/positioning.test.js +3 -3
  105. package/tests/query-limits.test.js +24 -8
  106. package/tests/relationship-endpoints.test.js +4 -5
  107. package/tests/relationships.test.js +0 -1
  108. package/tests/socketio.test.js +12 -1
  109. package/tests/virtual-fields.test.js +13 -13
  110. package/PLAN_FOR_WEBSOCKETS.md +0 -398
  111. package/README_1_Initial_Setup.md +0 -393
  112. package/README_X_Hooks.md +0 -647
  113. package/TODO_rest-api-plugin-extras.todo +0 -23
  114. package/connectors_ideas/express-plugin.js +0 -120
  115. package/connectors_ideas/http-plugin.js +0 -138
  116. package/docs/examples/express-plugin.js +0 -366
  117. package/example.js +0 -301
  118. package/plugins/core/HELPER_MODULES.md +0 -285
  119. package/plugins/core/lib/compile-schemas.js +0 -245
  120. package/plugins/core/lib/database-value-normalizers.js +0 -192
  121. package/plugins/core/lib/default-data-helpers.js +0 -100
  122. package/plugins/core/lib/knex-cross-table-search.js +0 -442
  123. package/plugins/core/lib/knex-json-api-helpers.js +0 -84
  124. package/plugins/core/lib/knex-json-api-transformers.js +0 -449
  125. package/plugins/core/lib/knex-pagination-helpers.js +0 -193
  126. package/plugins/core/lib/knex-window-queries.js +0 -142
  127. package/plugins/core/lib/many-to-many-manipulations.js +0 -343
  128. package/plugins/core/lib/relationship-processor.js +0 -278
  129. package/plugins/core/lib/schema-helpers.js +0 -343
  130. package/plugins/core/lib/scope-validations.js +0 -252
  131. package/plugins/core/rest-api-relationships-plugin.js +0 -700
  132. package/plugins/core/utils/knex-query-helpers-base.js +0 -56
  133. /package/{README_X_Permissions.md → docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md} +0 -0
  134. /package/{README_X_Positioning.md → docs/GUIDE/GUIDE_6_Positioning.md} +0 -0
  135. /package/{README_X_Bulk_Operations.md → docs/GUIDE/GUIDE_X_Bulk_Operations.md} +0 -0
  136. /package/{README_X_Cors.md → docs/GUIDE/GUIDE_X_Cors.md} +0 -0
  137. /package/{README_X_Multihome.md → docs/GUIDE/GUIDE_X_Multihome.md} +0 -0
  138. /package/{README_X_Appendices.md → docs/GUIDE/GUIDE_Y_Appendices.md} +0 -0
  139. /package/{QUERY_DEPTH_PROTECTION.md → docs/README_VARIOUS/QUERY_DEPTH_PROTECTION.md} +0 -0
  140. /package/{RELATIONSHIPS.md → docs/README_VARIOUS/RELATIONSHIPS.md} +0 -0
  141. /package/{WEBSOCKET_IMPLEMENTATION_STATUS.md → docs/README_VARIOUS/WEBSOCKET_IMPLEMENTATION_STATUS.md} +0 -0
  142. /package/{helper-functions-chart.md → docs/README_VARIOUS/helper-functions-chart.md} +0 -0
  143. /package/docs/{implementation-hidden-computed-fields.md → README_VARIOUS/implementation-hidden-computed-fields.md} +0 -0
  144. /package/docs/{include-limits-migration-guide.md → README_VARIOUS/include-limits-migration-guide.md} +0 -0
  145. /package/{readmeSnippets → docs/README_VARIOUS}/test_put_behavior.js +0 -0
  146. /package/{readmeSnippets → docs/README_VARIOUS}/test_put_fields_without_defaults.js +0 -0
@@ -0,0 +1,709 @@
1
+ # 2.6 Many to many (hasMany with through records)
2
+
3
+ Many-to-many relationships connect two resources through a pivot/junction table. For example, books can have multiple authors, and authors can write multiple books. This relationship is managed through a `book_authors` table that stores the connections.
4
+
5
+ Note that `book_authors` is not just a table, but a first class resource that you can manipulate like any other.
6
+
7
+ Let's create a complete example with books, authors, and their many-to-many relationship:
8
+
9
+ ```javascript
10
+ // Define books resource
11
+ await api.addResource('books', {
12
+ schema: {
13
+ title: { type: 'string', required: true, max: 255, search: true, indexed: true },
14
+ isbn: { type: 'string', max: 20, unique: true },
15
+ published_year: { type: 'number', min: 1900, max: 2100 }
16
+ },
17
+ relationships: {
18
+ authors: {
19
+ hasMany: 'authors',
20
+ through: 'book_authors', // The pivot table
21
+ foreignKey: 'book_id', // Column in pivot table pointing to books
22
+ otherKey: 'author_id' // Column in pivot table pointing to authors
23
+ }
24
+ },
25
+ searchSchema: {
26
+ title: { type: 'string', filterOperator: 'like' },
27
+
28
+ // Cross-table search through many-to-many relationship
29
+ authorName: {
30
+ type: 'string',
31
+ actualField: 'authors.name', // Search author names through the pivot
32
+ filterOperator: 'like'
33
+ }
34
+ }
35
+ });
36
+ await api.resources.books.createKnexTable();
37
+
38
+ // Define authors resource (already defined above, but showing the many-to-many side)
39
+ await api.addResource('authors', {
40
+ schema: {
41
+ name: { type: 'string', required: true, max: 100, search: true, indexed: true },
42
+ surname: { type: 'string', required: true, max: 100, search: true },
43
+ bio: { type: 'string', max: 1000, nullable: true }
44
+ },
45
+ relationships: {
46
+ books: {
47
+ hasMany: 'books',
48
+ through: 'book_authors', // Same pivot table
49
+ foreignKey: 'author_id', // Column in pivot table pointing to authors
50
+ otherKey: 'book_id' // Column in pivot table pointing to books
51
+ }
52
+ },
53
+ searchSchema: {
54
+ name: { type: 'string', filterOperator: 'like' },
55
+ surname: { type: 'string', filterOperator: 'like' },
56
+
57
+ // Cross-table search through many-to-many relationship
58
+ bookTitle: {
59
+ type: 'string',
60
+ actualField: 'books.title', // Search book titles through the pivot
61
+ filterOperator: 'like'
62
+ }
63
+ }
64
+ });
65
+ await api.resources.authors.createKnexTable();
66
+
67
+ // Define the pivot table resource
68
+ // This is optional but useful if you need to store additional data on the relationship
69
+ await api.addResource('book_authors', {
70
+ schema: {
71
+ book_id: { type: 'id', belongsTo: 'books', as: 'book', required: true },
72
+ author_id: { type: 'id', belongsTo: 'authors', as: 'author', required: true },
73
+ contribution_type: { type: 'string', max: 50, nullable: true }, // e.g., 'primary', 'co-author', 'editor'
74
+ royalty_percentage: { type: 'number', min: 0, max: 100, nullable: true }
75
+ }
76
+ // Note: Composite primary keys are not yet supported, but will be added in a future version
77
+ // For now, the table will use the default 'id' primary key
78
+ });
79
+ await api.resources.book_authors.createKnexTable();
80
+ ```
81
+
82
+ The key difference from regular hasMany relationships is the `through` property, which specifies the pivot table. Both `foreignKey` and `otherKey` are mandatory for many-to-many relationships.
83
+
84
+ Now let's create some data and explore how to work with many-to-many relationships:
85
+
86
+ ```javascript
87
+ // Create some authors
88
+ const author1 = await api.resources.authors.post({
89
+ name: 'Neil',
90
+ surname: 'Gaiman',
91
+ bio: 'British author of fiction, horror, and fantasy'
92
+ });
93
+
94
+ const author2 = await api.resources.authors.post({
95
+ name: 'Terry',
96
+ surname: 'Pratchett',
97
+ bio: 'English humorist and fantasy author'
98
+ });
99
+
100
+ const author3 = await api.resources.authors.post({
101
+ name: 'Stephen',
102
+ surname: 'King',
103
+ bio: 'American author of horror and supernatural fiction'
104
+ });
105
+
106
+ // Create books with authors - simplified mode
107
+ const goodOmens = await api.resources.books.post({
108
+ title: 'Good Omens',
109
+ isbn: '978-0060853983',
110
+ published_year: 1990,
111
+ authors: [author1.id, author2.id] // Co-authored by Gaiman and Pratchett
112
+ });
113
+
114
+ const americanGods = await api.resources.books.post({
115
+ title: 'American Gods',
116
+ isbn: '978-0380789030',
117
+ published_year: 2001,
118
+ authors: [author1.id] // Written by Gaiman alone
119
+ });
120
+
121
+ // Create a book using non-simplified mode
122
+ const theShining = await api.resources.books.post({
123
+ inputRecord: {
124
+ data: {
125
+ type: 'books',
126
+ attributes: {
127
+ title: 'The Shining',
128
+ isbn: '978-0307743657',
129
+ published_year: 1977
130
+ },
131
+ relationships: {
132
+ authors: {
133
+ data: [
134
+ { type: 'authors', id: author3.id }
135
+ ]
136
+ }
137
+ }
138
+ }
139
+ },
140
+ simplified: false
141
+ });
142
+ ```
143
+
144
+ ## Working with Pivot Tables Directly
145
+
146
+ Sometimes you need more control over the pivot table data, such as storing additional information about the relationship itself. There are two approaches:
147
+
148
+ ### 1. Creating relationships via the pivot table
149
+
150
+ Instead of using the `authors` field when creating a book, you can create the relationships directly through the pivot table. This is useful when you need to add extra data:
151
+
152
+ ```javascript
153
+ // Create a book without authors
154
+ const newBook = await api.resources.books.post({
155
+ title: 'The Color of Magic',
156
+ isbn: '978-0062225672',
157
+ published_year: 1983
158
+ });
159
+
160
+ // Then create the relationship with extra pivot data
161
+ await api.resources.book_authors.post({
162
+ book_id: newBook.id,
163
+ author_id: author2.id, // Terry Pratchett
164
+ contribution_type: 'primary',
165
+ royalty_percentage: 100
166
+ });
167
+ ```
168
+
169
+ ### 2. Updating existing pivot records
170
+
171
+ If you've already created relationships (e.g., using `authors: [author1.id, author2.id]` during book creation), you can update the pivot records to add extra data:
172
+
173
+ ```javascript
174
+ // First, find the pivot records for Good Omens
175
+ const pivotRecords = await api.resources.book_authors.query({
176
+ queryParams: {
177
+ filters: {
178
+ book_id: goodOmens.id
179
+ }
180
+ }
181
+ });
182
+
183
+ // Update each pivot record with extra data
184
+ for (const record of pivotRecords.data) {
185
+ await api.resources.book_authors.patch({
186
+ id: record.id,
187
+ contribution_type: 'co-author',
188
+ royalty_percentage: 50
189
+ });
190
+ }
191
+ ```
192
+
193
+ **Note:** Be careful not to create duplicate pivot records. If you use `authors: [...]` when creating a book, the pivot records are created automatically. Only manually create pivot records if you haven't used the relationship field during resource creation.
194
+
195
+ ## Including Many-to-Many Records (`include`)
196
+
197
+ When you fetch resources with many-to-many relationships, by default you only get the IDs:
198
+
199
+ ```javascript
200
+ const book_simplified = await api.resources.books.get({ id: goodOmens.id });
201
+ const book_non_simplified = await api.resources.books.get({ id: goodOmens.id, simplified: false });
202
+
203
+ console.log('Book without includes (simplified):', inspect(book_simplified));
204
+ console.log('Book without includes (non-simplified):', inspect(book_non_simplified));
205
+ ```
206
+
207
+ **Expected Output**
208
+
209
+ ```text
210
+ Book without includes (simplified):
211
+ {
212
+ id: '1',
213
+ title: 'Good Omens',
214
+ isbn: '978-0060853983',
215
+ published_year: 1990,
216
+ authors_ids: ['1', '2'] // Just the IDs
217
+ }
218
+
219
+ Book without includes (non-simplified):
220
+ {
221
+ data: {
222
+ type: 'books',
223
+ id: '1',
224
+ attributes: {
225
+ title: 'Good Omens',
226
+ isbn: '978-0060853983',
227
+ published_year: 1990
228
+ },
229
+ relationships: {
230
+ authors: {
231
+ data: [
232
+ { type: 'authors', id: '1' },
233
+ { type: 'authors', id: '2' }
234
+ ]
235
+ }
236
+ },
237
+ links: { self: '/api/1.0/books/1' }
238
+ },
239
+ links: { self: '/api/1.0/books/1' }
240
+ }
241
+ ```
242
+
243
+ ## Including Many to many Records (`include`)
244
+
245
+ To retrieve the full related resources through a many-to-many relationship, use the `include` query parameter:
246
+
247
+ ```javascript
248
+ // Get a book with its authors included
249
+ const book_with_authors_simplified = await api.resources.books.get({
250
+ id: goodOmens.id,
251
+ queryParams: { include: ['authors'] }
252
+ });
253
+
254
+ const book_with_authors_non_simplified = await api.resources.books.get({
255
+ id: goodOmens.id,
256
+ queryParams: { include: ['authors'] },
257
+ simplified: false
258
+ });
259
+
260
+ // Get all books with their authors
261
+ const books_with_authors_simplified = await api.resources.books.query({
262
+ queryParams: { include: ['authors'] }
263
+ });
264
+
265
+ const books_with_authors_non_simplified = await api.resources.books.query({
266
+ queryParams: { include: ['authors'] },
267
+ simplified: false
268
+ });
269
+
270
+ console.log('Book with authors (simplified):', inspect(book_with_authors_simplified));
271
+ console.log('Book with authors (non-simplified):', inspect(book_with_authors_non_simplified));
272
+ console.log('Books with authors (simplified):', inspect(books_with_authors_simplified));
273
+ console.log('Books with authors (non-simplified):', inspect(books_with_authors_non_simplified));
274
+ ```
275
+
276
+ **Expected Output**
277
+
278
+ ```text
279
+ Book with authors (simplified): {
280
+ id: '1',
281
+ title: 'Good Omens',
282
+ isbn: '978-0060853983',
283
+ published_year: 1990,
284
+ authors_ids: [ '1', '2' ],
285
+ authors: [
286
+ {
287
+ id: '1',
288
+ name: 'Neil',
289
+ surname: 'Gaiman',
290
+ bio: 'British author of fiction, horror, and fantasy',
291
+ books_ids: []
292
+ },
293
+ {
294
+ id: '2',
295
+ name: 'Terry',
296
+ surname: 'Pratchett',
297
+ bio: 'English humorist and fantasy author',
298
+ books_ids: []
299
+ }
300
+ ]
301
+ }
302
+ Book with authors (non-simplified): {
303
+ data: {
304
+ type: 'books',
305
+ id: '1',
306
+ attributes: {
307
+ title: 'Good Omens',
308
+ isbn: '978-0060853983',
309
+ published_year: 1990
310
+ },
311
+ relationships: {
312
+ authors: {
313
+ data: [ { type: 'authors', id: '1' }, { type: 'authors', id: '2' } ],
314
+ links: {
315
+ self: '/api/1.0/books/1/relationships/authors',
316
+ related: '/api/1.0/books/1/authors'
317
+ }
318
+ }
319
+ },
320
+ links: { self: '/api/1.0/books/1' }
321
+ },
322
+ included: [
323
+ {
324
+ type: 'authors',
325
+ id: '1',
326
+ attributes: {
327
+ name: 'Neil',
328
+ surname: 'Gaiman',
329
+ bio: 'British author of fiction, horror, and fantasy'
330
+ },
331
+ relationships: { books: { data: [] } },
332
+ links: { self: '/api/1.0/authors/1' }
333
+ },
334
+ {
335
+ type: 'authors',
336
+ id: '2',
337
+ attributes: {
338
+ name: 'Terry',
339
+ surname: 'Pratchett',
340
+ bio: 'English humorist and fantasy author'
341
+ },
342
+ relationships: { books: { data: [] } },
343
+ links: { self: '/api/1.0/authors/2' }
344
+ }
345
+ ],
346
+ links: { self: '/api/1.0/books/1' }
347
+ }
348
+ Books with authors (simplified): {
349
+ data: [
350
+ {
351
+ id: '1',
352
+ title: 'Good Omens',
353
+ isbn: '978-0060853983',
354
+ published_year: 1990,
355
+ authors_ids: [ '1', '2' ],
356
+ authors: [
357
+ {
358
+ id: '1',
359
+ name: 'Neil',
360
+ surname: 'Gaiman',
361
+ bio: 'British author of fiction, horror, and fantasy',
362
+ books_ids: []
363
+ },
364
+ {
365
+ id: '2',
366
+ name: 'Terry',
367
+ surname: 'Pratchett',
368
+ bio: 'English humorist and fantasy author',
369
+ books_ids: []
370
+ }
371
+ ]
372
+ },
373
+ {
374
+ id: '2',
375
+ title: 'American Gods',
376
+ isbn: '978-0380789030',
377
+ published_year: 2001,
378
+ authors_ids: [ '1' ],
379
+ authors: [
380
+ {
381
+ id: '1',
382
+ name: 'Neil',
383
+ surname: 'Gaiman',
384
+ bio: 'British author of fiction, horror, and fantasy',
385
+ books_ids: []
386
+ }
387
+ ]
388
+ },
389
+ {
390
+ id: '3',
391
+ title: 'The Shining',
392
+ isbn: '978-0307743657',
393
+ published_year: 1977,
394
+ authors_ids: [ '3' ],
395
+ authors: [
396
+ {
397
+ id: '3',
398
+ name: 'Stephen',
399
+ surname: 'King',
400
+ bio: 'American author of horror and supernatural fiction',
401
+ books_ids: []
402
+ }
403
+ ]
404
+ }
405
+ ],
406
+ links: { self: '/api/1.0/books?include=authors' }
407
+ }
408
+ Books with authors (non-simplified): {
409
+ data: [
410
+ {
411
+ type: 'books',
412
+ id: '1',
413
+ attributes: {
414
+ title: 'Good Omens',
415
+ isbn: '978-0060853983',
416
+ published_year: 1990
417
+ },
418
+ relationships: {
419
+ authors: {
420
+ data: [
421
+ { type: 'authors', id: '1' },
422
+ { type: 'authors', id: '2' }
423
+ ],
424
+ links: {
425
+ self: '/api/1.0/books/1/relationships/authors',
426
+ related: '/api/1.0/books/1/authors'
427
+ }
428
+ }
429
+ },
430
+ links: { self: '/api/1.0/books/1' }
431
+ },
432
+ {
433
+ type: 'books',
434
+ id: '2',
435
+ attributes: {
436
+ title: 'American Gods',
437
+ isbn: '978-0380789030',
438
+ published_year: 2001
439
+ },
440
+ relationships: {
441
+ authors: {
442
+ data: [ { type: 'authors', id: '1' } ],
443
+ links: {
444
+ self: '/api/1.0/books/2/relationships/authors',
445
+ related: '/api/1.0/books/2/authors'
446
+ }
447
+ }
448
+ },
449
+ links: { self: '/api/1.0/books/2' }
450
+ },
451
+ {
452
+ type: 'books',
453
+ id: '3',
454
+ attributes: {
455
+ title: 'The Shining',
456
+ isbn: '978-0307743657',
457
+ published_year: 1977
458
+ },
459
+ relationships: {
460
+ authors: {
461
+ data: [ { type: 'authors', id: '3' } ],
462
+ links: {
463
+ self: '/api/1.0/books/3/relationships/authors',
464
+ related: '/api/1.0/books/3/authors'
465
+ }
466
+ }
467
+ },
468
+ links: { self: '/api/1.0/books/3' }
469
+ }
470
+ ],
471
+ included: [
472
+ {
473
+ type: 'authors',
474
+ id: '1',
475
+ attributes: {
476
+ name: 'Neil',
477
+ surname: 'Gaiman',
478
+ bio: 'British author of fiction, horror, and fantasy'
479
+ },
480
+ relationships: { books: { data: [] } },
481
+ links: { self: '/api/1.0/authors/1' }
482
+ },
483
+ {
484
+ type: 'authors',
485
+ id: '2',
486
+ attributes: {
487
+ name: 'Terry',
488
+ surname: 'Pratchett',
489
+ bio: 'English humorist and fantasy author'
490
+ },
491
+ relationships: { books: { data: [] } },
492
+ links: { self: '/api/1.0/authors/2' }
493
+ },
494
+ {
495
+ type: 'authors',
496
+ id: '3',
497
+ attributes: {
498
+ name: 'Stephen',
499
+ surname: 'King',
500
+ bio: 'American author of horror and supernatural fiction'
501
+ },
502
+ relationships: { books: { data: [] } },
503
+ links: { self: '/api/1.0/authors/3' }
504
+ }
505
+ ],
506
+ links: { self: '/api/1.0/books?include=authors' }
507
+ }
508
+
509
+ ```
510
+
511
+ The include system automatically handles the JOIN through the pivot table. In simplified mode, the related resources are embedded directly. In JSON:API mode, they appear in the `included` array.
512
+
513
+ ## Search (many to many)
514
+
515
+ The search functionality for many-to-many relationships allows you to filter parent resources based on attributes of their related resources through the pivot table. This is particularly powerful for queries like "find all books written by authors named Neil" or "find all authors who wrote books with 'Gods' in the title".
516
+
517
+ Using the schema definitions from above, which include cross-table search fields:
518
+
519
+ ```javascript
520
+ // 1. Find books by author name (searches through the many-to-many relationship)
521
+ const books_by_neil_simplified = await api.resources.books.query({
522
+ queryParams: { filters: { authorName: 'Neil' } }
523
+ });
524
+
525
+ const books_by_neil_non_simplified = await api.resources.books.query({
526
+ queryParams: { filters: { authorName: 'Neil' } },
527
+ simplified: false
528
+ });
529
+
530
+ // 2. Find authors by book title (reverse search through many-to-many)
531
+ const authors_of_gods_books_simplified = await api.resources.authors.query({
532
+ queryParams: { filters: { bookTitle: 'Gods' } }
533
+ });
534
+
535
+ const authors_of_gods_books_non_simplified = await api.resources.authors.query({
536
+ queryParams: { filters: { bookTitle: 'Gods' } },
537
+ simplified: false
538
+ });
539
+
540
+ // 3. Combine searches: Find books by Neil that include full author data
541
+ const neil_books_with_authors = await api.resources.books.query({
542
+ queryParams: {
543
+ filters: { authorName: 'Neil' },
544
+ include: ['authors']
545
+ }
546
+ });
547
+
548
+ console.log('Books by Neil (simplified):', inspect(books_by_neil_simplified));
549
+ console.log('Books by Neil (non-simplified):', inspect(books_by_neil_non_simplified));
550
+ console.log('Authors who wrote books with "Gods" (simplified):', inspect(authors_of_gods_books_simplified));
551
+ console.log('Authors who wrote books with "Gods" (non-simplified):', inspect(authors_of_gods_books_non_simplified));
552
+ console.log('Neil books with full author data:', inspect(neil_books_with_authors));
553
+ ```
554
+
555
+ **Expected Output**
556
+
557
+ ```text
558
+ Books by Neil (simplified): {
559
+ data: [
560
+ {
561
+ id: '1',
562
+ title: 'Good Omens',
563
+ isbn: '978-0060853983',
564
+ published_year: 1990,
565
+ authors_ids: [ '1', '2' ]
566
+ },
567
+ {
568
+ id: '2',
569
+ title: 'American Gods',
570
+ isbn: '978-0380789030',
571
+ published_year: 2001,
572
+ authors_ids: [ '1' ]
573
+ }
574
+ ],
575
+ links: { self: '/api/1.0/books?filters[authorName]=Neil' }
576
+ }
577
+ Books by Neil (non-simplified): {
578
+ data: [
579
+ {
580
+ type: 'books',
581
+ id: '1',
582
+ attributes: {
583
+ title: 'Good Omens',
584
+ isbn: '978-0060853983',
585
+ published_year: 1990
586
+ },
587
+ relationships: {
588
+ authors: {
589
+ data: [
590
+ { type: 'authors', id: '1' },
591
+ { type: 'authors', id: '2' }
592
+ ]
593
+ }
594
+ },
595
+ links: { self: '/api/1.0/books/1' }
596
+ },
597
+ {
598
+ type: 'books',
599
+ id: '2',
600
+ attributes: {
601
+ title: 'American Gods',
602
+ isbn: '978-0380789030',
603
+ published_year: 2001
604
+ },
605
+ relationships: {
606
+ authors: { data: [ { type: 'authors', id: '1' } ] }
607
+ },
608
+ links: { self: '/api/1.0/books/2' }
609
+ }
610
+ ],
611
+ links: { self: '/api/1.0/books?filters[authorName]=Neil' }
612
+ }
613
+ Authors who wrote books with "Gods" (simplified): {
614
+ data: [
615
+ {
616
+ id: '1',
617
+ name: 'Neil',
618
+ surname: 'Gaiman',
619
+ bio: 'British author of fiction, horror, and fantasy',
620
+ books_ids: [ '1', '2' ]
621
+ }
622
+ ],
623
+ links: { self: '/api/1.0/authors?filters[bookTitle]=Gods' }
624
+ }
625
+ Authors who wrote books with "Gods" (non-simplified): {
626
+ data: [
627
+ {
628
+ type: 'authors',
629
+ id: '1',
630
+ attributes: {
631
+ name: 'Neil',
632
+ surname: 'Gaiman',
633
+ bio: 'British author of fiction, horror, and fantasy'
634
+ },
635
+ relationships: {
636
+ books: {
637
+ data: [ { type: 'books', id: '1' }, { type: 'books', id: '2' } ]
638
+ }
639
+ },
640
+ links: { self: '/api/1.0/authors/1' }
641
+ }
642
+ ],
643
+ links: { self: '/api/1.0/authors?filters[bookTitle]=Gods' }
644
+ }
645
+ Neil books with full author data: {
646
+ data: [
647
+ {
648
+ id: '1',
649
+ title: 'Good Omens',
650
+ isbn: '978-0060853983',
651
+ published_year: 1990,
652
+ authors_ids: [ '1', '2' ],
653
+ authors: [
654
+ {
655
+ id: '1',
656
+ name: 'Neil',
657
+ surname: 'Gaiman',
658
+ bio: 'British author of fiction, horror, and fantasy',
659
+ books_ids: []
660
+ },
661
+ {
662
+ id: '2',
663
+ name: 'Terry',
664
+ surname: 'Pratchett',
665
+ bio: 'English humorist and fantasy author',
666
+ books_ids: []
667
+ }
668
+ ]
669
+ },
670
+ {
671
+ id: '2',
672
+ title: 'American Gods',
673
+ isbn: '978-0380789030',
674
+ published_year: 2001,
675
+ authors_ids: [ '1' ],
676
+ authors: [
677
+ {
678
+ id: '1',
679
+ name: 'Neil',
680
+ surname: 'Gaiman',
681
+ bio: 'British author of fiction, horror, and fantasy',
682
+ books_ids: []
683
+ }
684
+ ]
685
+ }
686
+ ],
687
+ links: { self: '/api/1.0/books?filters[authorName]=Neil&include=authors' }
688
+ }
689
+ ```
690
+
691
+ The cross-table search through many-to-many relationships works by:
692
+ 1. Starting from the main table (e.g., books)
693
+ 2. JOINing through the pivot table (book_authors)
694
+ 3. JOINing to the related table (authors)
695
+ 4. Applying the filter on the related table's field
696
+
697
+ This generates SQL similar to:
698
+ ```sql
699
+ SELECT books.* FROM books
700
+ JOIN book_authors ON books.id = book_authors.book_id
701
+ JOIN authors ON book_authors.author_id = authors.id
702
+ WHERE authors.name LIKE '%Neil%'
703
+ ```
704
+
705
+ The system handles all the complexity of the double JOIN transparently, making it easy to search across many-to-many relationships without writing custom queries.
706
+
707
+ ---
708
+
709
+ [Previous: 2.5 hasMany records (polymorphic)](./GUIDE_2_5_HasMany_Polymorphic.md) | [Back to Guide](./README.md) | [Next: 2.7 Pagination and ordering](./GUIDE_2_7_Pagination_And_Ordering.md)