json-rest-api 1.0.29 → 2.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.
Files changed (294) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +744 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +733 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +103 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3838 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +37 -0
  44. package/index.js +3 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +70 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +120 -56
  71. package/plugins/core/lib/anyapi/stored-definition.js +109 -0
  72. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  73. package/plugins/core/lib/dbIntrospection.js +228 -21
  74. package/plugins/core/lib/dbTablesOperations.js +409 -92
  75. package/plugins/core/lib/fractional-positioning.js +12 -48
  76. package/plugins/core/lib/querying/identifier-query.js +19 -0
  77. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  78. package/plugins/core/lib/querying/include-to-many.js +428 -0
  79. package/plugins/core/lib/querying/include-to-one.js +579 -0
  80. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  81. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  82. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  83. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  84. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  85. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  86. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  87. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  88. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  89. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  90. package/plugins/core/lib/querying/query-constraint.js +27 -0
  91. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  92. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  93. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  94. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  95. package/plugins/core/lib/querying/url-helpers.js +11 -4
  96. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  97. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  98. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  99. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  100. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  101. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  102. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  103. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  104. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  105. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  106. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  107. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  108. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  109. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  110. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  111. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  112. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  113. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  114. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  115. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  116. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  117. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  118. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  119. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  120. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  121. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  122. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  123. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  124. package/plugins/core/lib/writing/resource-version.js +172 -0
  125. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  126. package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
  127. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  128. package/plugins/core/rest-api-cors-plugin.js +52 -205
  129. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  130. package/plugins/core/rest-api-label-plugin.js +25 -38
  131. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  132. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  133. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  134. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  135. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  136. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  137. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  138. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  139. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  140. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  141. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  142. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  143. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  144. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  145. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  146. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  147. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  148. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  149. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  150. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  151. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  152. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  153. package/plugins/core/rest-api-plugin.js +32 -46
  154. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  155. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  156. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  157. package/plugins/core/socketio-plugin.js +364 -344
  158. package/plugins/storage/local-storage.js +40 -36
  159. package/types/anyapi.d.ts +30 -0
  160. package/types/autofilter.d.ts +57 -0
  161. package/types/bulk-methods.d.ts +63 -0
  162. package/types/errors.d.ts +118 -0
  163. package/types/fastify-options.d.ts +19 -0
  164. package/types/file-detectors.d.ts +33 -0
  165. package/types/file-storage.d.ts +48 -0
  166. package/types/hook-context.d.ts +180 -0
  167. package/types/plugin-options.d.ts +65 -0
  168. package/types/relationship-methods.d.ts +74 -0
  169. package/types/representations.d.ts +50 -0
  170. package/types/resource-methods.d.ts +95 -0
  171. package/types/resource-schema.d.ts +157 -0
  172. package/types/row-policy.d.ts +31 -0
  173. package/types/runtime.d.ts +132 -0
  174. package/types/transactions.d.ts +21 -0
  175. package/.claude/agents/library-guide-expert.md +0 -30
  176. package/.claude/agents/rest-api-internals-expert.md +0 -54
  177. package/.claude/settings.local.json +0 -70
  178. package/AGENTS.md +0 -34
  179. package/CLAUDE.md +0 -160
  180. package/CRUSH.md +0 -55
  181. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  182. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  183. package/TODO.todo +0 -13
  184. package/connectors_ideas/fastify.js +0 -217
  185. package/connectors_ideas/koa-plugin.js +0 -112
  186. package/docs/.bundle/config +0 -2
  187. package/docs/COMPARISON.md +0 -433
  188. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  189. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  190. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  191. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  192. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  193. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  194. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  195. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  196. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  197. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  198. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  199. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  200. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  201. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  202. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  203. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  204. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  205. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  206. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  207. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  208. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  209. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  210. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  211. package/docs/Gemfile +0 -6
  212. package/docs/Gemfile.lock +0 -308
  213. package/docs/ONBOARDING.md +0 -315
  214. package/docs/_config.yml +0 -36
  215. package/docs/_layouts/default.html +0 -123
  216. package/docs/apple-touch-icon.svg +0 -4
  217. package/docs/assets/css/style.scss +0 -392
  218. package/docs/favicon.svg +0 -4
  219. package/docs/index.md +0 -164
  220. package/docs/mini-guides/file-uploads-complete.md +0 -161
  221. package/eslint.config.js +0 -13
  222. package/examples/url-override-example.js +0 -137
  223. package/fixing_plan.md +0 -112
  224. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  225. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  226. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  227. package/plugins/storage/s3-storage.js +0 -93
  228. package/quickTest.js +0 -136
  229. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  230. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  231. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  232. package/scripts/run-docs.js +0 -133
  233. package/test-debug.js +0 -59
  234. package/tests/README.md +0 -202
  235. package/tests/TEST_TEMPLATE.test.js +0 -178
  236. package/tests/anyapi-basic.test.js +0 -105
  237. package/tests/anyapi-cursor-pagination.test.js +0 -148
  238. package/tests/anyapi-custom-idproperty.test.js +0 -163
  239. package/tests/autofilter.test.js +0 -463
  240. package/tests/bulk-operations.test.js +0 -657
  241. package/tests/computed-fields.test.js +0 -586
  242. package/tests/cors.test.js +0 -834
  243. package/tests/curl.test.js +0 -467
  244. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  245. package/tests/custom-id-simplified-post.test.js +0 -75
  246. package/tests/custom-idproperty-relationships.test.js +0 -932
  247. package/tests/db-introspection.test.js +0 -521
  248. package/tests/db-table-operations.test.js +0 -798
  249. package/tests/fastify-plugin.test.js +0 -184
  250. package/tests/field-getters.test.js +0 -370
  251. package/tests/field-setters.test.js +0 -306
  252. package/tests/file-handling.test.js +0 -155
  253. package/tests/fixtures/api-configs.js +0 -2566
  254. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  255. package/tests/helpers/fake-fastify.js +0 -99
  256. package/tests/helpers/storage-mode.js +0 -65
  257. package/tests/helpers/test-utils.js +0 -203
  258. package/tests/http-connectors-parity.test.js +0 -395
  259. package/tests/id-normalization.test.js +0 -444
  260. package/tests/include-depth-validation.test.js +0 -331
  261. package/tests/includes.test.js +0 -427
  262. package/tests/local-storage.test.js +0 -96
  263. package/tests/logical-id-write-validation.test.js +0 -151
  264. package/tests/nested-includes.test.js +0 -617
  265. package/tests/pagination-boundaries.test.js +0 -154
  266. package/tests/pagination-contract.test.js +0 -122
  267. package/tests/pagination-cursor-multifield.test.js +0 -641
  268. package/tests/pagination-enhanced.test.js +0 -563
  269. package/tests/polymorphic-relationships.test.js +0 -347
  270. package/tests/polymorphic-simple.test.js +0 -148
  271. package/tests/positioning-advanced.test.js +0 -692
  272. package/tests/positioning-extended.test.js +0 -915
  273. package/tests/positioning-multifilter.test.js +0 -699
  274. package/tests/positioning.test.js +0 -560
  275. package/tests/post-commit-hooks.test.js +0 -120
  276. package/tests/projected-fields.test.js +0 -319
  277. package/tests/public-exports.test.js +0 -15
  278. package/tests/put-create-parity.test.js +0 -216
  279. package/tests/queries.test.js +0 -521
  280. package/tests/query-limits.test.js +0 -584
  281. package/tests/relationship-endpoints.test.js +0 -620
  282. package/tests/relationship-include-storage-adapter.test.js +0 -56
  283. package/tests/relationships.test.js +0 -681
  284. package/tests/rest-api.test.js +0 -337
  285. package/tests/return-record-settings.test.js +0 -184
  286. package/tests/row-policy.test.js +0 -489
  287. package/tests/s3-storage.test.js +0 -38
  288. package/tests/searchschema-knex-behavior.test.js +0 -187
  289. package/tests/searchschema-merge.test.js +0 -151
  290. package/tests/socketio.test.js +0 -860
  291. package/tests/storage-mapping.test.js +0 -362
  292. package/tests/temporal-boundaries.test.js +0 -208
  293. package/tests/test-development-guide.md +0 -370
  294. package/tests/virtual-fields.test.js +0 -404
@@ -1,808 +0,0 @@
1
- # 2.2 Manipulating and searching tables with no relationships
2
-
3
- ## Setting up the schema
4
-
5
- First of all, define a resource with a schema:
6
-
7
- ```javascript
8
- // Countries table
9
- await api.addResource('countries', {
10
- schema: {
11
- name: { type: 'string', required: true, max: 100, search: true },
12
- code: { type: 'string', max: 2, unique: true, search: true }, // ISO country code
13
- }
14
- });
15
- await api.resources.countries.createKnexTable()
16
- ```
17
-
18
- ## Adding data
19
-
20
- Note that we create the database table at the same time.
21
- This resource has just two fields, name and code, both searchable.
22
-
23
- Programmatically you can `post` and `get` easily:
24
-
25
- ```javascript
26
- const addedFrance = await api.resources.countries.post({ name: 'France', code: 'FR' });
27
- console.log('Added record :', inspect(addedFrance))
28
-
29
- const fetchedFrance = await api.resources.countries.get({ id: addedFrance.id });
30
- console.log('Refetched record:', inspect(fetchedFrance))
31
- ```
32
-
33
- After the logging messages, you will see:
34
-
35
- ```text
36
- Added record : { id: '1', name: 'France', code: 'FR' }
37
- Refetched record: { id: '1', name: 'France', code: 'FR' }
38
- Express server started on port 3000. API available at http://localhost:3000/api
39
- ```
40
-
41
- You can do the same thing talking to the server directly (although you will be dealing with JSON:API results).
42
- Leaving the code as it is, you will add nother country:
43
-
44
-
45
- ```bash
46
- $ curl -i -X POST -H "Content-Type: application/vnd.api+json" -d '{
47
- "data": {
48
- "type": "countries",
49
- "attributes": {
50
- "name": "United Kingdom",
51
- "code": "UK"
52
- }
53
- }
54
- }' http://localhost:3000/api/countries
55
- ```
56
- ```text
57
- HTTP/1.1 204 No Content
58
- X-Powered-By: Express
59
- Location: /api/countries/2
60
- ETag: W/"a-bAsFyilMr4Ra1hIU5PyoyFRunpI"
61
- Date: Wed, 23 Jul 2025 07:54:09 GMT
62
- Connection: keep-alive
63
- Keep-Alive: timeout=5
64
- ```
65
- ```bash
66
- $ curl -i -X GET http://localhost:3000/api/countries/2
67
- ```
68
- ```txt
69
- HTTP/1.1 200 OK
70
- X-Powered-By: Express
71
- Content-Type: application/vnd.api+json; charset=utf-8
72
- Content-Length: 169
73
- ETag: W/"a9-lnEVXaZ/V6qra0YgjpoEBUTZ3EY"
74
- Date: Wed, 23 Jul 2025 07:54:12 GMT
75
- Connection: keep-alive
76
- Keep-Alive: timeout=5
77
-
78
- {"data":{"type":"countries","id":"2","attributes":{"name":"United Kingdom","code":"UK"},"links":{"self":"/api/countries/2"}},"links":{"self":"/api/countries/2"}}
79
- ```
80
-
81
- Note that in "transport" mode after the POST the record was not returned to the client: instead, a status `204 No Content` was returned. However, the client will be aware of the ID of the newly created record thanks to the `Location` header.
82
-
83
- ## Manipulating data
84
-
85
- Replace the data commands with these:
86
-
87
- ```javascript
88
- await api.addResource('countries', {
89
- schema: {
90
- id: { type: 'string' },
91
- name: { type: 'string', required: true, max: 100, search: true, filterOperator: 'like' },
92
- code: { type: 'string', max: 2, unique: true, search: true }, // ISO country code
93
- }
94
- });
95
- await api.resources.countries.createKnexTable()
96
-
97
- const fr = await api.resources.countries.post({ name: 'France', code: 'FR' });
98
- const it = await api.resources.countries.post({ name: 'Italyy', code: 'IT' }); // Typo intentional
99
- const de = await api.resources.countries.post({ name: 'Germ', code: 'DE' }); // Typo intentional
100
-
101
- // Patching Germany. It will only change the name
102
- await api.resources.countries.patch({id: de.id, name: 'Germany' })
103
- let deFromDb = await api.resources.countries.get({ id: de.id });
104
-
105
- // Putting France. Note that this will actually reset any attributes that were not passed
106
- await api.resources.countries.put({id: it.id, name: 'Italy' })
107
- let itFromDb = await api.resources.countries.get({ id: it.id });
108
-
109
- console.log('Patched record (Germany):', inspect(deFromDb))
110
- console.log('Put record: (Italy)', inspect(itFromDb))
111
- ```
112
-
113
- The result will be:
114
-
115
- ```text
116
- Patched record (Germany): { id: '3', name: 'Germany', code: 'DE' }
117
- Put record: (Italy) { id: '2', name: 'Italy', code: null }
118
- ```
119
-
120
- As you can see, using PUT on Italy was a problem: since put didn't include the `code` field, and since PUT assumes a FULL record, the `code` field was reset to null. On the other hand, since the method PATCH assumes a partial update, the update for Germany did not _not_ overwrite the `code` field. This is a very important distinction, and it's the reason why most clients avoid PUT calls.
121
-
122
-
123
- ## Search (Filtering)
124
-
125
- The API supports powerful filtering capabilities out of the box for any fields you've marked as `search: true` in your schema.
126
-
127
- **Important Note about Query Results in Simplified Mode:**
128
-
129
- When using `query()` in simplified mode (the default for programmatic access), the return value is an object containing:
130
- - `data`: The array of matching records
131
- - `meta`: Metadata about the results (e.g., pagination info)
132
- - `links`: Links for pagination and related resources
133
-
134
- For example:
135
- ```javascript
136
- const result = await api.resources.countries.query({ /* ... */ });
137
- // result is: { data: [...], meta: {...}, links: {...} }
138
- // To access the records: result.data
139
- ```
140
-
141
- **Programmatic Example: Searching for countries**
142
-
143
- Change the code to add some countries:
144
-
145
- ```javascript
146
- const fr = await api.resources.countries.post({ name: 'France', code: 'FR' });
147
- const it = await api.resources.countries.post({ name: 'Italy', code: 'IT' });
148
- const de = await api.resources.countries.post({ name: 'Germany', code: 'DE' });
149
- const au = await api.resources.countries.post({ name: 'Australia', code: 'AU' });
150
- const at = await api.resources.countries.post({ name: 'Austria', code: 'AT' });
151
- const ge = await api.resources.countries.post({ name: 'Georgia', code: 'GE' });
152
-
153
- const searchAustralia = await api.resources.countries.query({
154
- queryParams: {
155
- filters: {
156
- name: 'Australia'
157
- }
158
- }
159
- });
160
- // HTTP: GET /api/countries?filter[name]=Australia
161
- // Returns: {
162
- // data: [{ id: '2', name: 'Australia', code: 'AU' }]
163
- // }
164
-
165
- const searchAustr = await api.resources.countries.query({
166
- queryParams: {
167
- filters: {
168
- name: 'Austr'
169
- }
170
- }
171
- });
172
- // HTTP: GET /api/countries?filter[name]=Austr
173
- // Returns: {
174
- // data: []
175
- // }
176
-
177
- console.log('Search for "Australia":', inspect(searchAustralia))
178
- console.log('Search for "Austr":', inspect(searchAustr))
179
- // Note: searchAustralia and searchAustr contain { data, meta, links } objects
180
- ```
181
-
182
- The result will be:
183
-
184
- ```
185
- Search for Australia: { data: [ { id: '4', name: 'Australia', code: 'AU' } ], meta: {...}, links: {...} }
186
- Search for "Austr": { data: [], meta: {...}, links: {...} }
187
- ```
188
-
189
- It's clear that the search is only matching precise results.
190
-
191
-
192
- There are two ways to enable search on a field in your schema. The first one is the one we are currently using, with `search: true`.
193
- As seen above, the only time filtering works is when there is an exact match.
194
- However, rather than `true` or `false`, `search` can also be an object.
195
- Changing the definition of `name` in the `countries` resource to this:
196
-
197
- ```javascript
198
- name: { type: 'string', required: true, max: 100, search: { filterOperator: 'like' } },
199
- ```
200
-
201
- Will give you the expected results:
202
-
203
- ```text
204
- Search for Australia: [ { id: '4', name: 'Australia', code: 'AU' } ]
205
- Search for "Austr": [
206
- { id: '4', name: 'Australia', code: 'AU' },
207
- { id: '5', name: 'Austria', code: 'AT' }
208
- ]
209
- ```
210
-
211
- This is the
212
-
213
- **Available operators for `filterOperator`:**
214
- - `'='` - Exact match (default)
215
- - `'like'` - Partial match with % wildcards automatically added on both sides
216
- - `'>'`, `'>='`, `'<'`, `'<='` - Comparison operators for numeric/date fields
217
- - Other SQL operators are passed through directly (e.g., `'!='`, `'<>'`)
218
-
219
- Note: Only `'like'` receives special handling (automatic % wildcards). All other operators are passed directly to the SQL query.
220
-
221
- You can also define multiple search patterns from a single field:
222
-
223
- ```javascript
224
- await api.addResource('countries', {
225
- schema: {
226
- name: {
227
- type: 'string', required: true, search: {
228
- name: { filterOperator: '=', type: 'string' },
229
- nameLike: { filterOperator: 'like', type: 'string' }
230
- }
231
- },
232
- code: { type: 'string', unique: true, search: true }
233
- }
234
- });
235
- await api.resources.countries.createKnexTable()
236
-
237
- await api.resources.countries.post({ name: 'Georgia', code: 'GE' });
238
- await api.resources.countries.post({ name: 'France', code: 'FR' });
239
- await api.resources.countries.post({ name: 'Italy', code: 'IT' });
240
- await api.resources.countries.post({ name: 'Germany', code: 'DE' });
241
- await api.resources.countries.post({ name: 'Australia', code: 'AU' });
242
- await api.resources.countries.post({ name: 'Austria', code: 'AT' });
243
-
244
- const searchAustralia = await api.resources.countries.query({
245
- queryParams: {
246
- filters: {
247
- name: 'Australia'
248
- }
249
- }
250
- });
251
- // HTTP: GET /api/countries?filter[name]=Australia
252
- // Returns: {
253
- // data: [{ id: '2', name: 'Australia', code: 'AU' }]
254
- // }
255
-
256
- const searchAustr = await api.resources.countries.query({
257
- queryParams: {
258
- filters: {
259
- nameLike: 'Austr'
260
- }
261
- }
262
- });
263
- // HTTP: GET /api/countries?filter[nameLike]=Austr
264
- // Returns: {
265
- // data: [{ id: '2', name: 'Australia', code: 'AU' }, { id: '3', name: 'Austria', code: 'AT' }]
266
- // }
267
-
268
- console.log('Search for "Australia":', inspect(searchAustralia))
269
- console.log('Search for "Austr":', inspect(searchAustr))
270
- ```
271
-
272
- This is very powerful in that it allows you to define multiple ways of filtering a field depending on needs.
273
-
274
- There is another, even more powerful way to define how to search in a resource: define a whole searchSchema that is completely independent to the main schema.
275
-
276
- Under the hood, `rest-api-plugin` actually creates a `searchSchema` object based on the option on the default schema. However, it's very possible for an attribute to define a `searchSchema` directly:
277
-
278
-
279
- ```javascript
280
- await api.addResource('countries', {
281
- schema: {
282
- name: { type: 'string', required: true, max: 100 },
283
- code: { type: 'string', max: 2, unique: true }
284
- },
285
- searchSchema: {
286
- // Define searchable fields explicitly
287
- name: { type: 'string', filterOperator: '=' },
288
- code: { type: 'string', filterOperator: '=' },
289
- nameLike: { type: 'string', actualField: 'name', filterOperator: 'like' }
290
- }
291
- });
292
- await api.resources.countries.createKnexTable()
293
- ```
294
-
295
- Note that the definition above is functionally _identical_ to the one provided a few paragraphs above.
296
-
297
- **Important: When you define a `searchSchema`, it takes precedence over fields marked with `search: true` in the main schema.** However, fields marked with `search: true` that are NOT in the explicit `searchSchema` will be automatically added to enable searching. This allows you to:
298
- - Use `searchSchema` to override specific field behaviors
299
- - Still benefit from `search: true` markers for additional fields
300
- - Have the explicit `searchSchema` always win in case of conflicts
301
-
302
- The `searchSchema` gives you:
303
-
304
- * Complete control where needed: Override specific fields while keeping the convenience of `search: true` for others
305
- * No mixing of concerns: Your data schema stays focused on data validation and storage, while searchSchema handles complex search behavior
306
- * Flexibility to extend: Add virtual fields or complex filters via searchSchema while basic fields use `search: true`
307
- * Best of both worlds: Explicit control for complex cases, automatic setup for simple ones
308
-
309
- `searchSchema` also defines the public filter names your API accepts. Those keys do not need to match database columns or even resource field names. Treat `searchSchema` as an allowlist plus normalization layer for filter input: the actual meaning of each key is still implemented by the backend through mappings such as `actualField`, multi-field rules such as `oneOf`, standard operators, or custom `applyFilter` logic. It is not a portable filter DSL.
310
-
311
- The merge rules and validation surface are shared across storage backends. Advanced execution semantics are still backend-owned. In practice, that means alias filters such as `oneOf` and custom `applyFilter` functions should be validated against the backend you actually run, and the SQL-heavy examples in this chapter assume the Knex-backed query layer.
312
-
313
- `searchSchema` also gives you the ability to define a search field that will search in multiple fields. For example:
314
-
315
- ```javascript
316
- searchSchema: {
317
- search: {
318
- type: 'string',
319
- oneOf: ['name', 'code'],
320
- filterOperator: 'like'
321
- }
322
- }
323
-
324
- const searchGe = await api.resources.countries.query({
325
- queryParams: {
326
- filters: {
327
- search: 'ge'
328
- }
329
- }
330
- });
331
- // HTTP: GET /api/countries?filter[search]=ge
332
- // Returns: {
333
- // data: [{ id: '3', name: 'Georgia', code: 'GE' }, { id: '6', name: 'Germany', code: 'DE' }]
334
- // }
335
-
336
- console.log('Search for "ge":', inspect(searchGe))
337
- ```
338
-
339
- Will return:
340
-
341
- ```
342
- Search for "ge": { data: [
343
- { id: '3', name: 'Georgia', code: 'GE' },
344
- { id: '6', name: 'Germany', code: 'DE' }
345
- ], meta: {...}, links: {...} }
346
- ```
347
-
348
- This common pattern will give you the ability to create "global" search fields that will look in multiple fields.
349
-
350
- ### Merging searchSchema with search:true Fields
351
-
352
- When you combine `searchSchema` with fields marked `search: true`, the library intelligently merges them:
353
-
354
- ```javascript
355
- await api.addResource('products', {
356
- schema: {
357
- name: { type: 'string', search: true }, // Will be added to searchSchema
358
- description: { type: 'string', search: true }, // Will be added to searchSchema
359
- price: { type: 'number', search: true }, // Overridden by searchSchema below
360
- category_id: { type: 'number' }, // Not searchable
361
- sku: { type: 'string', search: true } // Will be added to searchSchema
362
- },
363
- searchSchema: {
364
- // Explicit searchSchema entries take precedence
365
- price: {
366
- type: 'number',
367
- filterOperator: 'between' // Overrides the search:true with custom operator
368
- },
369
- category_name: { // Virtual field (doesn't exist in schema)
370
- type: 'string',
371
- actualField: 'category.name',
372
- filterOperator: 'like'
373
- }
374
- }
375
- });
376
-
377
- // Result: The effective searchSchema includes:
378
- // - name (from search:true, uses default '=' operator)
379
- // - description (from search:true, uses default '=' operator)
380
- // - sku (from search:true, uses default '=' operator)
381
- // - price (from explicit searchSchema, uses 'between' operator)
382
- // - category_name (from explicit searchSchema, virtual field)
383
-
384
- // You can now search using all these fields:
385
- const results = await api.resources.products.query({
386
- queryParams: {
387
- filters: {
388
- name: 'Widget', // From search:true
389
- price: [10, 100], // From searchSchema with 'between'
390
- category_name: 'Electronics' // Virtual field from searchSchema
391
- }
392
- }
393
- });
394
- ```
395
-
396
- This merge behavior gives you the best of both worlds:
397
- - Quick setup with `search: true` for simple fields
398
- - Precise control with `searchSchema` for complex requirements
399
- - No need to repeat fields in searchSchema that already have `search: true`
400
-
401
- ### Multi-word Search with AND Logic
402
-
403
- The `oneOf` search feature becomes even more powerful when combined with `splitBy` and `matchAll` options. This allows you to search for multiple words where ALL must appear somewhere in the specified fields.
404
-
405
- ```javascript
406
- await api.addResource('countries', {
407
- schema: {
408
- name: { type: 'string', required: true },
409
- code: { type: 'string', unique: true }
410
- },
411
- searchSchema: {
412
- search: {
413
- type: 'string',
414
- oneOf: ['name', 'code'],
415
- filterOperator: 'like',
416
- splitBy: ' ', // Split search terms by space
417
- matchAll: true // Require ALL terms to match (AND logic)
418
- }
419
- }
420
- });
421
- await api.resources.countries.createKnexTable()
422
- ```
423
-
424
- With this configuration, searching becomes much more precise:
425
-
426
- ```javascript
427
- // Add some countries
428
- await api.resources.countries.post({ name: 'United States', code: 'US' });
429
- await api.resources.countries.post({ name: 'United Kingdom', code: 'UK' });
430
- await api.resources.countries.post({ name: 'United Arab Emirates', code: 'AE' });
431
- await api.resources.countries.post({ name: 'South Africa', code: 'ZA' });
432
-
433
- // Search for "united states" - both words must appear
434
- const results = await api.resources.countries.query({
435
- queryParams: {
436
- filters: {
437
- search: 'united states'
438
- }
439
- }
440
- });
441
- // HTTP: GET /api/countries?filter[search]=united%20states
442
- // Returns: {
443
- // data: [{ id: '1', name: 'United States', code: 'US' }]
444
- // }
445
-
446
- console.log('Found:', results);
447
- // Note: results contains { data, meta, links } - access results.data for the array
448
- // Note: results now contains { data, meta, links } - access results.data for the array
449
- // Returns: [{ id: '1', name: 'United States', code: 'US' }]
450
- // Does NOT return United Kingdom or United Arab Emirates
451
- ```
452
-
453
- **How it works:**
454
-
455
- 1. The search term "united states" is split by space into ["united", "states"]
456
- 2. With `matchAll: true`, the query requires BOTH terms to appear
457
- 3. Each term can appear in ANY of the fields listed in `oneOf`
458
- 4. The SQL generated looks like:
459
-
460
- ```sql
461
- WHERE (
462
- (countries.name LIKE '%united%' OR countries.code LIKE '%united%')
463
- AND
464
- (countries.name LIKE '%states%' OR countries.code LIKE '%states%')
465
- )
466
- ```
467
-
468
- **More examples:**
469
-
470
- ```javascript
471
- // Search for "south africa" - finds only South Africa
472
- const southAfrica = await api.resources.countries.query({
473
- queryParams: { filters: { search: 'south africa' } }
474
- });
475
- // HTTP: GET /api/countries?filter[search]=south%20africa
476
- // Returns: {
477
- // data: [{ id: '4', name: 'South Africa', code: 'ZA' }]
478
- // }
479
-
480
- // Search for "united arab" - finds only United Arab Emirates
481
- const uae = await api.resources.countries.query({
482
- queryParams: { filters: { search: 'united arab' } }
483
- });
484
- // HTTP: GET /api/countries?filter[search]=united%20arab
485
- // Returns: {
486
- // data: [{ id: '3', name: 'United Arab Emirates', code: 'AE' }]
487
- // }
488
-
489
- // Single word searches still work normally
490
- const allUnited = await api.resources.countries.query({
491
- queryParams: { filters: { search: 'united' } }
492
- });
493
- // HTTP: GET /api/countries?filter[search]=united
494
- // Returns: {
495
- // data: [
496
- // { id: '1', name: 'United States', code: 'US' },
497
- // { id: '2', name: 'United Kingdom', code: 'UK' },
498
- // { id: '3', name: 'United Arab Emirates', code: 'AE' }
499
- // ]
500
- // }
501
- ```
502
-
503
- **Alternative configurations:**
504
-
505
- You can also use different separators and OR logic:
506
-
507
- ```javascript
508
- searchSchema: {
509
- // Comma-separated OR search
510
- tags: {
511
- type: 'string',
512
- oneOf: ['tags', 'categories', 'keywords'],
513
- filterOperator: 'like',
514
- splitBy: ',', // Split by comma
515
- matchAll: false // OR logic (default) - match ANY term
516
- },
517
-
518
- // Exact match with AND logic
519
- codes: {
520
- type: 'string',
521
- oneOf: ['primary_code', 'secondary_code'],
522
- filterOperator: '=', // Exact match
523
- splitBy: ' ',
524
- matchAll: true // All codes must match exactly
525
- }
526
- }
527
- ```
528
-
529
- This feature is particularly useful for:
530
- - Full-text search functionality where users type multiple words
531
- - Tag or keyword searches where all terms must be present
532
- - Product searches matching multiple criteria
533
- - Finding records that match complex multi-word queries
534
-
535
- ### Custom Search Functions
536
-
537
- If you need even more complex searches, you can use `searchSchema` to define search fields with custom query logic:
538
-
539
- ```javascript
540
- await api.addResource('countries', {
541
- schema: {
542
- name: { type: 'string', required: true },
543
- code: { type: 'string', unique: true }
544
- },
545
- searchSchema: {
546
- // Standard fields
547
- name: { type: 'string', filterOperator: '=' },
548
- code: { type: 'string', filterOperator: '=' },
549
-
550
- // Custom search using a function
551
- nameOrCode: {
552
- type: 'string',
553
- applyFilter: function(query, filterValue) {
554
- // Custom SQL logic: case-insensitive search in name OR exact match on code
555
- query.where(function() {
556
- this.whereRaw('LOWER(name) LIKE LOWER(?)', [`%${filterValue}%`])
557
- .orWhereRaw('LOWER(code) = LOWER(?)', [filterValue]);
558
- });
559
- }
560
- }
561
- }
562
- });
563
- await api.resources.countries.createKnexTable()
564
-
565
- await api.resources.countries.post({ name: 'United States', code: 'US' });
566
- await api.resources.countries.post({ name: 'United Kingdom', code: 'UK' });
567
- await api.resources.countries.post({ name: 'United Arab Emirates', code: 'AE' });
568
- await api.resources.countries.post({ name: 'South Africa', code: 'ZA' });
569
- ```
570
-
571
- When `filterOperator` is a function instead of an operator string, it receives:
572
- - `query` - The Knex query builder instance
573
- - `filterValue` - The search value from the user
574
- - `fieldName` - The name of the search field (optional third parameter)
575
-
576
- This gives you complete control over the SQL generated for that search field.
577
-
578
- **Example usage:**
579
-
580
- ```javascript
581
- // This custom search will find both:
582
- // - Countries with "at" in the name (United States, United Kingdom, United Arab Emirates)
583
- // - Countries with code "at"
584
- const results = await api.resources.countries.query({
585
- queryParams: {
586
- filters: {
587
- nameOrCode: 'at'
588
- }
589
- }
590
- });
591
- // HTTP: GET /api/countries?filter[nameOrCode]=at
592
- // Returns: {
593
- // data: [
594
- // { id: '1', name: 'United States', code: 'US' },
595
- // { id: '2', name: 'United Kingdom', code: 'UK' },
596
- // { id: '3', name: 'United Arab Emirates', code: 'AE' },
597
- // { id: '5', name: 'Austria', code: 'AT' }
598
- // ]
599
- // }
600
- ```
601
-
602
- The result is:
603
-
604
- ```text
605
- Query results: [
606
- { id: '1', name: 'United States', code: 'US' },
607
- { id: '3', name: 'United Arab Emirates', code: 'AE' }
608
- ]
609
- ```
610
-
611
- The function approach is powerful for:
612
- - Case-insensitive searches
613
- - Complex conditions combining multiple fields
614
- - Database-specific functions
615
- - Custom business logic in searches
616
-
617
- Since `applyFilter` functions tend to be database-dependent, it's best to avoid using it unless necessary.
618
-
619
- ### Sparse Fieldsets
620
-
621
- The JSON:API specification includes a powerful feature called "sparse fieldsets" that allows you to request only specific fields from a resource. This is essential for optimizing API performance by reducing payload sizes and network traffic.
622
-
623
- **How Sparse Fieldsets Work:**
624
-
625
- By default, API responses include all fields defined in the schema. With sparse fieldsets, you can specify exactly which fields you want returned. The `id` field is always included automatically as it's required by JSON:API.
626
-
627
- Let's work with our countries table:
628
-
629
- ```javascript
630
- await api.addResource('countries', {
631
- schema: {
632
- name: { type: 'string', required: true, max: 100, search: true },
633
- code: { type: 'string', max: 2, unique: true, search: true }
634
- }
635
- });
636
- await api.resources.countries.createKnexTable()
637
-
638
- // Add some test data
639
- await api.resources.countries.post({ name: 'France', code: 'FR' });
640
- await api.resources.countries.post({ name: 'Germany', code: 'DE' });
641
- await api.resources.countries.post({ name: 'Italy', code: 'IT' });
642
- await api.resources.countries.post({ name: 'United Kingdom', code: 'UK' });
643
- await api.resources.countries.post({ name: 'United States', code: 'US' });
644
- ```
645
-
646
- **Sparse Fieldsets with `get()` - Single Record:**
647
-
648
- ```javascript
649
- // Fetch a single country with all fields (default behavior)
650
- const fullCountry = await api.resources.countries.get({ id: '1' });
651
- console.log('Full record:', inspect(fullCountry));
652
-
653
- // Fetch only the name field
654
- const nameOnly = await api.resources.countries.get({
655
- id: '1',
656
- queryParams: { fields: { countries: 'name' } }
657
- });
658
- console.log('Name only:', inspect(nameOnly));
659
-
660
- // Fetch only the code field
661
- const codeOnly = await api.resources.countries.get({
662
- id: '1',
663
- queryParams: { fields: { countries: 'code' } }
664
- });
665
- console.log('Code only:', inspect(codeOnly));
666
- ```
667
-
668
- The output will be:
669
-
670
- ```text
671
- Full record: { id: '1', name: 'France', code: 'FR' }
672
- Name only: { id: '1', name: 'France' }
673
- Code only: { id: '1', code: 'FR' }
674
- ```
675
-
676
- **Sparse Fieldsets with `query()` - Multiple Records:**
677
-
678
- Sparse fieldsets become even more valuable when fetching collections, as they can significantly reduce the response size:
679
-
680
- ```javascript
681
- // Query all countries starting with 'United' - full records
682
- const fullRecords = await api.resources.countries.query({
683
- queryParams: {
684
- filters: { name: 'United' }
685
- }
686
- });
687
- // HTTP: GET /api/countries?filter[name]=United
688
- // Returns: {
689
- // data: [
690
- // { id: '4', name: 'United Kingdom', code: 'UK' },
691
- // { id: '5', name: 'United States', code: 'US' }
692
- // ]
693
- // }
694
-
695
- console.log('Full records:', inspect(fullRecords));
696
- // Note: fullRecords contains { data, meta, links }
697
-
698
- // Query with only names returned
699
- const namesOnly = await api.resources.countries.query({
700
- queryParams: {
701
- filters: { name: 'United' },
702
- fields: { countries: 'name' }
703
- }
704
- });
705
- // HTTP: GET /api/countries?filter[name]=United&fields[countries]=name
706
- // Returns: {
707
- // data: [
708
- // { id: '4', name: 'United Kingdom' },
709
- // { id: '5', name: 'United States' }
710
- // ]
711
- // }
712
-
713
- console.log('Names only:', inspect(namesOnly));
714
- // Note: namesOnly contains { data, meta, links }
715
-
716
- // Query with only codes returned
717
- const codesOnly = await api.resources.countries.query({
718
- queryParams: {
719
- filters: { name: 'United' },
720
- fields: { countries: 'code' }
721
- }
722
- });
723
- // HTTP: GET /api/countries?filter[name]=United&fields[countries]=code
724
- // Returns: {
725
- // data: [
726
- // { id: '4', code: 'UK' },
727
- // { id: '5', code: 'US' }
728
- // ]
729
- // }
730
-
731
- console.log('Codes only:', inspect(codesOnly));
732
- // Note: codesOnly contains { data, meta, links }
733
- ```
734
-
735
- **Combining Sparse Fieldsets with Complex Searches:**
736
-
737
- Sparse fieldsets work seamlessly with all search features, including our new multi-word search:
738
-
739
- ```javascript
740
- // Define a countries resource with multi-word search
741
- await api.addResource('countries', {
742
- schema: {
743
- name: { type: 'string', required: true },
744
- code: { type: 'string', unique: true },
745
- population: { type: 'integer' },
746
- continent: { type: 'string' }
747
- },
748
- searchSchema: {
749
- search: {
750
- type: 'string',
751
- oneOf: ['name', 'continent'],
752
- filterOperator: 'like',
753
- splitBy: ' ',
754
- matchAll: true
755
- }
756
- }
757
- });
758
-
759
- // Add countries with more fields
760
- await api.resources.countries.post({
761
- name: 'South Africa',
762
- code: 'ZA',
763
- population: 59308690,
764
- continent: 'Africa'
765
- });
766
- await api.resources.countries.post({
767
- name: 'South Korea',
768
- code: 'KR',
769
- population: 51269185,
770
- continent: 'Asia'
771
- });
772
-
773
- // Search for "south africa" but return only name and population
774
- const sparseSearch = await api.resources.countries.query({
775
- queryParams: {
776
- filters: { search: 'south africa' },
777
- fields: { countries: 'name,population' }
778
- }
779
- });
780
- // HTTP: GET /api/countries?filter[search]=south%20africa&fields[countries]=name,population
781
- // Returns: {
782
- // data: [{ id: '1', name: 'South Africa', population: 59308690 }]
783
- // }
784
-
785
- console.log('Sparse search result:', inspect(sparseSearch));
786
- // Note: sparseSearch contains { data, meta, links }
787
- ```
788
-
789
- **Important Notes:**
790
-
791
- 1. **The `id` field is always included** - This is required by the JSON:API specification
792
- 2. **Field names must match schema** - Requesting non-existent fields will be ignored
793
- 3. **Improves performance** - Especially important for large records or when fetching many records
794
- 4. **Works with relationships** - When we cover relationships, you'll see how to apply sparse fieldsets to related resources too
795
-
796
- **HTTP API Usage:**
797
-
798
- When using the HTTP API, sparse fieldsets are specified as comma-separated values:
799
-
800
- ```
801
- GET /api/countries?fields[countries]=name,code
802
- GET /api/countries/1?fields[countries]=name
803
- GET /api/countries?filter[search]=united+states&fields[countries]=code
804
- ```
805
-
806
- ---
807
-
808
- [Previous: 2.1 The starting point](./GUIDE_2_1_The_Starting_Point.md) | [Back to Guide](./README.md) | [Next: 2.3 `belongsTo` Relationships](./GUIDE_2_3_BelongsTo_Relationships.md)