json-rest-api 1.0.6 → 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 +99 -33
  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,955 @@
1
+ # Basic usage and basic configuration
2
+
3
+ This section explains how to set up `json-rest-api` in your code.
4
+
5
+ ## Defining the Basic Tables
6
+
7
+ The documentation uses a consistent example throughout - a book catalog system with authors, publishers, and countries.
8
+
9
+ **Important**: The five tables defined below (countries, publishers, authors, books, and book_authors) form the foundation for all examples, tests, and documentation in this guide. We'll consistently reference this same schema structure to demonstrate all features of the library. At times, we will change the definition of some of them to show specific features.
10
+
11
+ Also for brevity, the `inspect()` function will be assumed to be set.
12
+
13
+ Also, since we are using them, you will need to install:
14
+
15
+ ```bash
16
+ npm install json-rest-api
17
+ npm install knex
18
+ npm install better-sqlite3
19
+ ```
20
+
21
+ You won't need to install `hooned-api` since it's already a dependency of json-rest-api.
22
+
23
+ So this is the first basic script:
24
+
25
+ ```javascript
26
+ import { RestApiPlugin, RestApiKnexPlugin } from 'json-rest-api';
27
+ import { Api } from 'hooked-api';
28
+ import knexLib from 'knex';
29
+ import util from 'util';
30
+
31
+ // Utility used throughout this guide
32
+ const inspect = (obj) => util.inspect(obj, { depth: 5 })
33
+
34
+ // Create a Knex instance connected to SQLite in-memory database
35
+ const knex = knexLib({
36
+ client: 'better-sqlite3',
37
+ connection: {
38
+ filename: ':memory:'
39
+ },
40
+ useNullAsDefault: true
41
+ });
42
+
43
+ // Create API instance
44
+ const api = new Api({ name: 'book-catalog-api', version: '1.0.0' });
45
+
46
+ // Install plugins
47
+ await api.use(RestApiPlugin, { publicBaseUrl: '/api/1.0' });
48
+ await api.use(RestApiKnexPlugin, { knex });
49
+
50
+ // Define schemas for our book catalog system
51
+
52
+ // Countries table
53
+ await api.addResource('countries', {
54
+ schema: {
55
+ name: { type: 'string', required: true, max: 100, search: true },
56
+ code: { type: 'string', max: 2, unique: true, search: true }, // ISO country code
57
+ },
58
+ relationships: {
59
+ publishers: { hasMany: 'publishers', foreignKey: 'country_id' },
60
+ books: { hasMany: 'books', foreignKey: 'country_id' }
61
+ },
62
+ });
63
+ await api.resources.countries.createKnexTable()
64
+
65
+
66
+ /// *** ...programmatic calls here... ***
67
+
68
+ // Close the database connection (since there is no server waiting)
69
+ await knex.destroy();
70
+ console.log('\nAll schemas created successfully!');
71
+ console.log('Database connection closed.');
72
+ ```
73
+
74
+ This set of resources cover a lot of ground in terms of relationships etc. Those other tables will be covered in the next part of this guide.
75
+
76
+ #### Loglevels
77
+
78
+ The available log levels in hooked-api are (from most verbose to least verbose):
79
+
80
+ 1. `trace` - Most verbose, shows everything including internal operations
81
+ 2. `debug` - Debug information for development
82
+ 3. `info` - Informational messages (DEFAULT)
83
+ 4. `warn` - Only warnings and errors
84
+ 5. `error` - Only error messages
85
+ 6. `silent` - No logging at all
86
+
87
+ To change loglevels, pass a logLevel option to the API:
88
+
89
+ ```javascript
90
+ const api = new Api({
91
+ name: 'book-catalog-api',
92
+ version: '1.0.0',
93
+ logLevel: 'warn' // Only show warnings and errors
94
+ });
95
+ ```
96
+
97
+ By default, the INFO level logs you're seeing are the default. To reduce them, you could use:
98
+
99
+ - logLevel: `warn` - Only see warnings and errors
100
+ - logLevel: `error` - Only see errors
101
+ - logLevel: `silent` - No logs at all
102
+
103
+ To see more detail for debugging:
104
+
105
+ - logLevel: `debug` - More detailed information
106
+ - logLevel: `trace` - Everything, including hook executions and internal operations
107
+
108
+ ### Database Options
109
+
110
+ The `json-rest-knex-plugin` plugin uses `knex` as its database abstraction layer, which supports a wide variety of SQL databases.
111
+ In the example above, we configured `knex` to use an in-memory SQLite database for simplicity:
112
+
113
+ ```javascript
114
+ const knex = knexLib({
115
+ client: 'sqlite3',
116
+ connection: {
117
+ filename: ':memory:' // In-memory database for quick examples
118
+ },
119
+ useNullAsDefault: true // Recommended for SQLite
120
+ });
121
+ ```
122
+
123
+ To connect to a different database, you would simply change the `client` and `connection` properties in the `knexLib` configuration. Here are a few common examples:
124
+
125
+ **PostgreSQL:**
126
+
127
+ ```javascript
128
+ const knex = knexLib({
129
+ client: 'pg', // PostgreSQL client
130
+ connection: {
131
+ host: '127.0.0.1',
132
+ user: 'your_username',
133
+ password: 'your_password',
134
+ database: 'your_database_name',
135
+ port: 5432 // Default PostgreSQL port
136
+ }
137
+ });
138
+ ```
139
+ **MySQL / MariaDB:**
140
+
141
+ ```javascript
142
+ const knex = knexLib({
143
+ client: 'mysql', // or 'mariasql' for MariaDB
144
+ connection: {
145
+ host: '127.0.0.1',
146
+ user: 'your_username',
147
+ password: 'your_password',
148
+ database: 'your_database_name',
149
+ port: 3306 // Default MySQL/MariaDB port
150
+ }
151
+ });
152
+ ```
153
+
154
+ Remember to install the corresponding `knex` driver for your chosen database (e.g., `npm install pg` for PostgreSQL, `npm install mysql2` for MySQL) just as we had to `npm install` the `better-sqlite3` package to make the first example work.
155
+
156
+ ### Programmatic Usage
157
+
158
+ The `json-rest-api` plugin extends your `hooked-api` instance with powerful RESTful capabilities, allowing you to interact with your defined resources both programmatically within your application code and via standard HTTP requests.
159
+
160
+ The instanced object becomes a fully-fledged, database and schema aware API.
161
+
162
+ Once your resources are defined using `api.addResource()`, you can directly call CRUD (Create, Read, Update, Delete) methods on `api.resources.<resourceName>`.
163
+
164
+ Let's start by creating a `country` record:
165
+
166
+ ```javascript
167
+ // Example: Create a country
168
+ const countryUs = await api.resources.countries.post({
169
+ name: 'United States',
170
+ code: 'US'
171
+ });
172
+ console.log('Created Country:', inspect(countryUs));
173
+ // Expected Output:
174
+ // Created Country: { id: '1', name: 'United States', code: 'US' }
175
+ ```
176
+
177
+ Now, let's retrieve this country data using its ID:
178
+
179
+ ```javascript
180
+ // Example: Refetch a country by ID
181
+ const countryUsRefetched = await api.resources.countries.get({
182
+ id: countryUs.id, // Use the ID returned from the POST operation
183
+ });
184
+ console.log('Refetched Country:', inspect(countryUsRefetched));
185
+ // Expected Output:
186
+ // Refetched Country: { id: '1', name: 'United States', code: 'US' }
187
+ ```
188
+
189
+ The database is populated, and the newly added record is then fetched.
190
+
191
+ #### API usage and simplified mode
192
+
193
+ In the examples above, we're using the API in **simplified mode** (which is the default for programmatic usage). Simplified mode is a convenience feature that allows you to work with plain JavaScript objects instead of the full JSON:API document structure. However, it's important to understand that internally, everything is still processed as proper JSON:API documents.
194
+
195
+ Simplified mode changes:
196
+
197
+ - **Input**: You can pass plain objects with just the attributes
198
+ - **Output**: You get back plain objects with id and attributes merged at the top level
199
+
200
+ Here's how the same operations look when **NOT** using simplified mode:
201
+
202
+ ```javascript
203
+ // Create a country (non-simplified mode)
204
+ const countryUs = await api.resources.countries.post({
205
+ inputRecord: {
206
+ data: {
207
+ type: 'countries',
208
+ attributes: {
209
+ name: 'United States',
210
+ code: 'US'
211
+ }
212
+ }
213
+ },
214
+ simplified: false
215
+ });
216
+ console.log('Created Country:', inspect(countryUs));
217
+ // Expected Output:
218
+ // Created Country: {
219
+ // data: {
220
+ // type: 'countries',
221
+ // id: '1',
222
+ // attributes: { name: 'United States', code: 'US' },
223
+ // links: { self: '/api/1.0/countries/1' }
224
+ // },
225
+ // links: { self: '/api/1.0/countries/1' }
226
+ // }
227
+
228
+ // Fetch a country by ID (non-simplified mode)
229
+ const countryUsRefetched = await api.resources.countries.get({
230
+ id: countryUs.data.id,
231
+ simplified: false
232
+ });
233
+ console.log('Refetched Country:', inspect(countryUsRefetched));
234
+ // Expected Output (a full JSON:API record):
235
+ // Refetched Country: {
236
+ // data: {
237
+ // type: 'countries',
238
+ // id: '1',
239
+ // attributes: { name: 'United States', code: 'US' },
240
+ // links: { self: '/api/1.0/countries/1' }
241
+ // },
242
+ // links: { self: '/api/1.0/countries/1' }
243
+ // }
244
+ ```
245
+
246
+ (Note that the full JSON:API record includes links to resources, which use `publicBaseUrl` set in when `use()`ing the `json-rest-api` plugin.)
247
+
248
+ As you can see, when `simplified: false` is used:
249
+
250
+ - Input requires the full JSON:API document structure with `data`, `type`, and `attributes`
251
+ - Output returns the full JSON:API response with the same nested structure (and links)
252
+ - You need to access the ID as `result.data.id` instead of just `result.id`
253
+
254
+ **NOTE**: For programmatic API calls, simplified mode defaults to true but can be configured at multiple levels: globally via `simplifiedApi: true/false` when installing RestApiPlugin, per-resource when calling `addResource()`, or per-call by setting `simplified: true/false` in the call parameters, with the hierarchy being per-call → per-resource → global default; additionally, when passing attributes directly (without inputRecord), simplified mode is always true regardless of configuration.
255
+
256
+ For example:
257
+
258
+ 1. **Global default**: Set during plugin installation
259
+ ```javascript
260
+ await api.use(RestApiPlugin, {
261
+ simplifiedApi: false, // All API calls will use JSON:API format by default
262
+ simplifiedTransport: true // All HTTP calls will use simplified format by default
263
+ });
264
+ ```
265
+
266
+ 2. **Per-resource override**: Set when defining a resource
267
+ ```javascript
268
+ await api.addResource('countries', {
269
+ schema: {
270
+ name: { type: 'string', required: true },
271
+ code: { type: 'string', required: true }
272
+ },
273
+ simplifiedApi: false, // API calls to this resource use JSON:API format
274
+ simplifiedTransport: true // HTTP calls to this resource use simplified format
275
+ });
276
+ ```
277
+
278
+ NOTE: this can also be written as:
279
+
280
+ ```javascript
281
+ await api.addResource('countries', {
282
+ schema: {
283
+ name: { type: 'string', required: true },
284
+ code: { type: 'string', required: true }
285
+ },
286
+
287
+ },{
288
+ // Parameters set directly into 'vars'
289
+ vars: {
290
+ simplifiedApi: false, // API calls to this resource use JSON:API format
291
+ simplifiedTransport: true // HTTP calls to this resource use simplified format
292
+ }
293
+ }
294
+ );
295
+ ```
296
+
297
+
298
+ 3. **Per-call override**: Set in individual method calls
299
+ ```javascript
300
+ // Force non-simplified for this call only
301
+ const result = await api.resources.countries.post({
302
+ inputRecord: {
303
+ data: {
304
+ type: 'countries',
305
+ attributes: {
306
+ name: 'United States',
307
+ code: 'US'
308
+ }
309
+ }
310
+ },
311
+ simplified: false
312
+ });
313
+
314
+ ```
315
+
316
+ The hierarchy is: **per-call → per-resource (parameters or variables) → global default**
317
+
318
+ **Important**: The resource-level configuration supports separate settings for API and transport modes, allowing you to have different behaviors for programmatic calls versus HTTP endpoints for the same resource.
319
+
320
+ **Special case**: When passing attributes directly (without `inputRecord`), simplified mode is always `true` regardless of configuration:
321
+ ```javascript
322
+ // This ALWAYS uses simplified mode, even if global/resource setting is false
323
+ const result = await api.resources.countries.post({
324
+ name: 'United States',
325
+ code: 'US'
326
+ });
327
+ ```
328
+
329
+ By default, `simplifiedApi` is `true` for programmatic usage, making it easier to work with the API in your code while still maintaining full JSON:API compliance internally.
330
+
331
+ #### API usage and returning records
332
+
333
+ When performing write operations (POST, PUT, PATCH), you can control what data is returned. This is useful for balancing between getting complete data and optimizing performance.
334
+
335
+ There are TWO separate settings for this:
336
+
337
+ 1. **`returnRecordApi`** - Controls what **programmatic API calls** return (default: `'full'`)
338
+ 2. **`returnRecordTransport`** - Controls what **HTTP/REST endpoints** return (default: `'no'`)
339
+
340
+ This separation allows you to have different behaviors for internal API usage versus external HTTP clients. For example, your internal code might want full records for convenience, while HTTP clients might prefer minimal responses for performance.
341
+
342
+ Both settings accept three string values:
343
+ - **`'full'`**: Returns the complete record with all attributes, relationships, computed fields, and links
344
+ - **`'minimal'`**: Returns only the resource type and ID
345
+ - **`'no'`**: Returns nothing (undefined in programmatic calls, 204 No Content in HTTP)
346
+
347
+ Here's how these settings work:
348
+
349
+ ```javascript
350
+ // Example 1: Using defaults
351
+ const api = new Api({ name: 'api', version: '1.0.0' });
352
+ await api.use(RestApiPlugin);
353
+ // Default: returnRecordApi='full', returnRecordTransport='no'
354
+
355
+ // Programmatic API call returns full record by default
356
+ const country = await api.resources.countries.post({
357
+ name: 'Canada',
358
+ code: 'CA'
359
+ });
360
+ console.log('API result:', country);
361
+ // Expected Output:
362
+ // API result: { id: '1', name: 'Canada', code: 'CA' }
363
+
364
+ // But the same operation via HTTP returns 204 No Content by default
365
+ // POST /api/countries -> 204 No Content (no body)
366
+
367
+ // Example 2: Different settings for API and Transport
368
+ await api.use(RestApiPlugin, {
369
+ returnRecordApi: 'minimal', // API calls return minimal
370
+ returnRecordTransport: 'full' // HTTP calls return full
371
+ });
372
+
373
+ // API call returns minimal
374
+ const apiResult = await api.resources.countries.post({
375
+ name: 'Mexico',
376
+ code: 'MX'
377
+ });
378
+ console.log('API result:', apiResult);
379
+ // Expected Output:
380
+ // API result: { id: '2', type: 'countries' }
381
+
382
+ // HTTP call returns full record
383
+ // POST /api/countries -> 204 No Content
384
+ // Body: { data: { type: 'countries', id: '3', attributes: { name: 'Mexico', code: 'MX' } } }
385
+
386
+ // Example 3: Per-method configuration
387
+ await api.use(RestApiPlugin, {
388
+ returnRecordApi: {
389
+ post: 'full', // API POST returns full
390
+ put: 'minimal', // API PUT returns minimal
391
+ patch: 'no' // API PATCH returns nothing
392
+ },
393
+ returnRecordTransport: {
394
+ post: 'minimal', // HTTP POST returns minimal
395
+ put: 'no', // HTTP PUT returns 204
396
+ patch: 'full' // HTTP PATCH returns full
397
+ }
398
+ });
399
+ ```
400
+
401
+ When combined with non-simplified mode, the difference is even more apparent:
402
+
403
+ ```javascript
404
+ // Non-simplified mode with full record
405
+ const fullJsonApi = await api.resources.countries.post({
406
+ inputRecord: {
407
+ data: {
408
+ type: 'countries',
409
+ attributes: { name: 'France', code: 'FR' }
410
+ }
411
+ },
412
+ simplified: false
413
+ });
414
+ console.log('Full JSON:API response:', inspect(fullJsonApi));
415
+ // Expected Output:
416
+ // Full JSON:API response: {
417
+ // data: {
418
+ // type: 'countries',
419
+ // id: '4',
420
+ // attributes: { name: 'France', code: 'FR' },
421
+ // links: { self: '/api/1.0/countries/4' }
422
+ // },
423
+ // links: { self: '/api/1.0/countries/4' }
424
+ // }
425
+
426
+ // Non-simplified mode with minimal return
427
+ const minimalJsonApi = await api.resources.countries.post({
428
+ inputRecord: {
429
+ data: {
430
+ type: 'countries',
431
+ attributes: { name: 'Germany', code: 'DE' }
432
+ }
433
+ },
434
+ simplified: false
435
+ });
436
+ console.log('Minimal JSON:API response:', inspect(minimalJsonApi));
437
+ // Expected Output:
438
+ // Minimal JSON:API response: { id: '5', type: 'countries' }
439
+ ```
440
+
441
+ **Configuration Levels**: Both `returnRecordApi` and `returnRecordTransport` can be configured at multiple levels, with the hierarchy being: per-call → per-resource → global default.
442
+
443
+ **Important**: Like the simplified settings, the resource-level configuration supports separate settings for API and transport modes, allowing fine-grained control over what data is returned for programmatic calls versus HTTP endpoints.
444
+
445
+ For example:
446
+
447
+ 1. **Global default**: Set during plugin installation
448
+ ```javascript
449
+ await api.use(RestApiPlugin, {
450
+ returnRecordApi: {
451
+ post: 'full', // API POST returns full
452
+ put: 'minimal', // API PUT returns minimal
453
+ patch: 'full' // API PATCH returns full
454
+ },
455
+ returnRecordTransport: {
456
+ post: 'minimal', // HTTP POST returns minimal
457
+ put: 'no', // HTTP PUT returns 204
458
+ patch: 'minimal' // HTTP PATCH returns minimal
459
+ }
460
+ });
461
+ ```
462
+
463
+ 2. **Per-resource override**: Set when defining a resource
464
+ ```javascript
465
+ await api.addResource('countries', {
466
+ schema: {
467
+ name: { type: 'string', required: true },
468
+ code: { type: 'string', required: true }
469
+ },
470
+ returnRecordApi: 'full', // All API methods return full
471
+ returnRecordTransport: 'minimal' // All HTTP methods return minimal
472
+ });
473
+
474
+ // Or with per-method granularity:
475
+ await api.addResource('products', {
476
+ schema: {
477
+ name: { type: 'string', required: true },
478
+ price: { type: 'number', required: true }
479
+ },
480
+ returnRecordApi: {
481
+ post: 'full', // API POST returns full record
482
+ put: 'minimal', // API PUT returns minimal
483
+ patch: 'no' // API PATCH returns nothing
484
+ },
485
+ returnRecordTransport: {
486
+ post: 'minimal', // HTTP POST returns minimal
487
+ put: 'no', // HTTP PUT returns 204
488
+ patch: 'full' // HTTP PATCH returns full record
489
+ }
490
+ });
491
+ ```
492
+
493
+ 3. **Per-call override**: Set in individual method calls
494
+ ```javascript
495
+ // Override for a specific API call
496
+ const result = await api.resources.countries.patch({
497
+ inputRecord: {
498
+ id: '1',
499
+ name: 'United States of America'
500
+ },
501
+ returnFullRecord: 'minimal' // Overrides the configured setting
502
+ });
503
+ // result = { id: '1', type: 'countries' }
504
+ ```
505
+
506
+ **Performance consideration**: When using `'full'`, the API performs an additional GET request internally after the write operation to fetch the complete record with all computed fields and relationships. Using `'minimal'` or `'no'` skips this extra query, improving performance when you don't need the full data.
507
+
508
+ **Remember the defaults**:
509
+ - `returnRecordApi` defaults to `'full'` (convenient for development)
510
+ - `returnRecordTransport` defaults to `'no'` (optimal for performance)
511
+
512
+ ### REST Usage (HTTP Endpoints)
513
+
514
+ Since this is a REST API, its main purpose is to be used with a REST interface over HTTP.
515
+ To expose your API resources via HTTP, you need to install one of the connector plugins:
516
+
517
+ * **`ExpressPlugin`**: If you are using `Express.js` in your application.
518
+
519
+ * **`(Coming soon)`**: Fastify and Koa are planned and coming soon
520
+
521
+ Thanks to the ExpressPlugin, `json-rest-api` is able to export an Express router that you can just `use()` in Express.
522
+
523
+ Just modify the example above so that it looks like this:
524
+
525
+ ```javascript
526
+ import { RestApiPlugin, RestApiKnexPlugin, ExpressPlugin } from 'json-rest-api'; // Added: ExpressPlugin
527
+ import { Api } from 'hooked-api';
528
+ import knexLib from 'knex';
529
+ import util from 'util';
530
+ import express from 'express'; // Added: Express
531
+
532
+ // Utility used throughout this guide
533
+ const inspect = (obj) => util.inspect(obj, { depth: 5 })
534
+
535
+ // Create a Knex instance connected to SQLite in-memory database
536
+ const knex = knexLib({
537
+ client: 'better-sqlite3',
538
+ connection: {
539
+ filename: ':memory:'
540
+ },
541
+ useNullAsDefault: true
542
+ });
543
+
544
+ // Create API instance
545
+ const api = new Api({ name: 'book-catalog-api', version: '1.0.0' });
546
+
547
+ // Install plugins
548
+ await api.use(RestApiPlugin, { publicBaseUrl: '/api/1.0' });
549
+ await api.use(RestApiKnexPlugin, { knex });
550
+ await api.use(ExpressPlugin, { mountPath: '/api' }); // Added: Express Plugin
551
+
552
+ // Countries table
553
+ await api.addResource('countries', {
554
+ schema: {
555
+ name: { type: 'string', required: true, max: 100, search: true },
556
+ code: { type: 'string', max: 2, unique: true, search: true }, // ISO country code
557
+ },
558
+ relationships: {
559
+ publishers: { hasMany: 'publishers', foreignKey: 'country_id' },
560
+ books: { hasMany: 'books', foreignKey: 'country_id' }
561
+ },
562
+ });
563
+ await api.resources.countries.createKnexTable()
564
+
565
+ /// *** ...programmatic calls here... ***
566
+
567
+ // Createthe express server and add the API's routes
568
+ const app = express();
569
+ app.use(api.http.express.router);
570
+ app.use(api.http.express.notFoundRouter);
571
+
572
+ app.listen(3000, () => {
573
+ console.log('Express server started on port 3000. API available at http://localhost:3000/api');
574
+ });
575
+
576
+ // Close the database connection // no longer happening since the server stays on
577
+ // await knex.destroy();
578
+ // console.log('\n✅ All schemas created successfully!');
579
+ // console.log('Database connection closed.');
580
+ ```
581
+
582
+ Since you added `express`, you will need to install it:
583
+
584
+ ```bash
585
+ npm install express
586
+ ```
587
+
588
+ Note how the `HttpPlugin` doesn't actually add any routes to the server. All it does, is expose `api.http.express.router` which is a
589
+
590
+ Once the server is running, you can interact with your API using tools like `curl`.
591
+
592
+ **REST Example: Create a Country**
593
+
594
+ ```bash
595
+ curl -i -X POST -H "Content-Type: application/vnd.api+json" \
596
+ -d '{
597
+ "data": {
598
+ "type": "countries",
599
+ "attributes": {
600
+ "name": "United Kingdom",
601
+ "code": "UK"
602
+ }
603
+ }
604
+ }' http://localhost:3000/api/countries
605
+ ```
606
+
607
+ This will have no response (204 No Content) since by default resources won't return anything when using HTTP:
608
+
609
+ ```
610
+ HTTP/1.1 204 No Content
611
+ X-Powered-By: Express
612
+ Location: http://localhost:3000/api/countries/1
613
+ ETag: W/"a-bAsFyilMr4Ra1hIU5PyoyFRunpI"
614
+ Date: Tue, 22 Jul 2025 14:54:45 GMT
615
+ Connection: keep-alive
616
+ Keep-Alive: timeout=5
617
+ ```
618
+
619
+ **REST Example: Get a Country by ID**
620
+
621
+ ```bash
622
+ curl -X GET http://localhost:3000/api/countries/2
623
+ ```
624
+
625
+ The result:
626
+
627
+ ```json
628
+ {
629
+ "data": {
630
+ "type": "countries",
631
+ "id": "1",
632
+ "attributes": {
633
+ "name": "United Kingdom",
634
+ "code": "UK"
635
+ },
636
+ "links": {
637
+ "self": "http://localhost:3000/api/countries/1"
638
+ }
639
+ },
640
+ "links": {
641
+ "self": "http://localhost:3000/api/countries/1"
642
+ }
643
+ }
644
+ ```
645
+
646
+ **From now on, all examples will provide both programmatic usage and `curl` usage.** This ensures you understand how to interact with the API directly in your code and how it translates to standard HTTP requests.
647
+
648
+ ### Simplified Mode
649
+
650
+ The simplified mode concept works exactly the same way over HTTP as it does for programmatic API calls (see "API usage and simplified mode" above). However, there's an important difference in the defaults:
651
+
652
+ - **Programmatic API**: `simplifiedApi` defaults to `true` (convenient for developers)
653
+ - **HTTP/REST**: `simplifiedTransport` defaults to `false` (JSON:API compliance)
654
+
655
+ This means that by default, HTTP endpoints expect and return proper JSON:API format:
656
+
657
+ Most production servers will keep `simplifiedTransport: false` to maintain JSON:API compliance for client applications. You can enable simplified mode for HTTP if needed:
658
+
659
+ ```javascript
660
+ await api.use(RestApiPlugin, {
661
+ simplifiedTransport: true // Enable simplified mode for HTTP (not recommended)
662
+ });
663
+ ```
664
+
665
+ The result:
666
+
667
+ ```json
668
+ {
669
+ "id":"1",
670
+ "name":"United Kingdom",
671
+ "code":"UK"
672
+ }
673
+ ```
674
+
675
+ Keep in mind that to get this result you will need to:
676
+
677
+ 1) Amend your test file, adding `simplifiedTransport: true` to the RestApiPlugin
678
+ 2) Restart your server (CTRL-C and re-run it)
679
+ 3) Re-add a country with the POST Curl command shown earlier
680
+ 4) Finally, re-fetch it and see the record in simplified form.
681
+
682
+ Once again, it will be uncommon to use the simplified version for the HTTP transport, but it can be used to satisfy legacy clients etc.
683
+
684
+ ### Return Record Settings for HTTP
685
+
686
+ The `returnRecordTransport` setting controls what HTTP/REST endpoints return (see "API usage and returning records" above for full details). The HTTP status codes vary based on the operation and setting:
687
+
688
+ **POST operations:**
689
+ - `returnRecordTransport: 'full'` → Returns `204 No Content` with the full record in the body
690
+ - `returnRecordTransport: 'minimal'` → Returns `204 No Content` with minimal response `{ id: '...', type: '...' }`
691
+ - `returnRecordTransport: 'no'` → Returns `204 No Content` with no body
692
+
693
+ **PUT/PATCH operations:**
694
+ - `returnRecordTransport: 'full'` → Returns `204 No Content` with the full record in the body
695
+ - `returnRecordTransport: 'minimal'` → Returns `204 No Content` with minimal response `{ id: '...', type: '...' }`
696
+ - `returnRecordTransport: 'no'` → Returns `204 No Content` with no body
697
+
698
+ **DELETE operations:**
699
+ - Always returns `204 No Content` with no body (regardless of settings)
700
+
701
+ **Remember**: The default for `returnRecordTransport` is `'no'`, which means HTTP write operations return 204 No Content by default. This is different from programmatic API calls which default to returning full records.
702
+
703
+ # A practical example
704
+
705
+ If you want your server to reply with a full record, you can set it this way:
706
+
707
+ ```javascript
708
+ await api.use(RestApiPlugin, {
709
+ returnRecordTransport: 'full'
710
+ });
711
+ ```
712
+
713
+ Restart once again the server. Then add a country using cUrl:
714
+
715
+ ```bash
716
+ curl -i -X POST -H "Content-Type: application/vnd.api+json" \
717
+ -d '{
718
+ "data": {
719
+ "type": "countries",
720
+ "attributes": {
721
+ "name": "United Kingdom",
722
+ "code": "UK"
723
+ }
724
+ }
725
+ }' http://localhost:3000/api/countries
726
+ ```
727
+ The result:
728
+
729
+ ```
730
+ HTTP/1.1 204 No Content
731
+ X-Powered-By: Express
732
+ Content-Type: application/vnd.api+json; charset=utf-8
733
+ Location: http://localhost:3000/api/countries/1
734
+ Content-Length: 203
735
+ ETag: W/"cb-ycYSy+lmxv51HwwBAEPFd465J8M"
736
+ Date: Tue, 22 Jul 2025 15:14:13 GMT
737
+ Connection: keep-alive
738
+ Keep-Alive: timeout=5
739
+
740
+ {
741
+ "data": {
742
+ "type": "countries",
743
+ "id": "1",
744
+ "attributes": {
745
+ "name": "United Kingdom",
746
+ "code": "UK"
747
+ },
748
+ "links": {
749
+ "self": "http://localhost:3000/api/countries/1"
750
+ }
751
+ },
752
+ "links": {
753
+ "self": "http://localhost:3000/api/countries/1"
754
+ }
755
+ }
756
+ ```
757
+
758
+ # Plugin and resource variables
759
+
760
+ When passing a parameter, `rest-api-plugin` normalises them (when needed) and stores them into plugin variables. This means that these two ways of defining `returnRecordApi` is identical:
761
+
762
+ ```javascript
763
+ await api.use(RestApiPlugin, { publicBaseUrl: '/api/1.0', returnRecordTransport: 'minimal'});
764
+
765
+ // ...or...
766
+
767
+ await api.use(RestApiPlugin, {
768
+ publicBaseUrl: '/api/1.0',
769
+ vars: {
770
+ returnRecordTransport: 'minimal'
771
+ }
772
+ });
773
+ ```
774
+
775
+ Here is a full list of parameters and their respective variables:
776
+
777
+ | Parameter | Variable Name | Default Value | Description | Scope Override |
778
+ |-----------|--------------|---------------|-------------|----------------|
779
+ | `queryDefaultLimit` | `vars.queryDefaultLimit` | `25` | Default number of records returned in query results | ✓ |
780
+ | `queryMaxLimit` | `vars.queryMaxLimit` | `100` | Maximum allowed limit for query results | ✓ |
781
+ | `includeDepthLimit` | `vars.includeDepthLimit` | `3` | Maximum depth for nested relationship includes | ✓ |
782
+ | `publicBaseUrl` | `vars.publicBaseUrl` | `''` | Base URL for generated links (e.g., `https://api.example.com/v1`) | ✓ |
783
+ | `enablePaginationCounts` | `vars.enablePaginationCounts` | `true` | Whether to include total count in pagination metadata | ✓ |
784
+ | `simplifiedApi` | `vars.simplifiedApi` | `true` | Use simplified format for programmatic API calls | ✓ |
785
+ | `simplifiedTransport` | `vars.simplifiedTransport` | `false` | Use simplified format for HTTP/REST endpoints | ✓ |
786
+ | `idProperty` | `vars.idProperty` | `'id'` | Name of the ID field in resources | ✓ |
787
+ | `returnRecordApi` | `vars.returnRecordApi` | `{ post: 'full', put: 'full', patch: 'full' }` | What to return for programmatic API write operations | ✓ |
788
+ | `returnRecordTransport` | `vars.returnRecordTransport` | `{ post: 'no', put: 'no', patch: 'no' }` | What to return for HTTP/REST write operations | ✓ |
789
+
790
+ **Resource-specific parameters** (only available at resource level, not plugin level):
791
+
792
+ | Parameter | Variable Name | Default Value | Description |
793
+ |-----------|--------------|---------------|-------------|
794
+ | `sortableFields` | `vars.sortableFields` | `[]` | Array of field names that can be used for sorting |
795
+ | `defaultSort` | `vars.defaultSort` | `null` | Default sort order for queries (e.g., `['-createdAt', 'name']`) |
796
+
797
+ **Notes:**
798
+ - "Scope Override" indicates whether the parameter can be overridden at the resource (scope) level
799
+ - `returnRecordApi` and `returnRecordTransport` can be either:
800
+ - A string: `'no'`, `'minimal'`, or `'full'` (applies to all methods)
801
+ - An object: `{ post: 'full', put: 'minimal', patch: 'no' }` (per-method configuration)
802
+ - All parameters support the cascade: per-call → resource-level → plugin-level default
803
+
804
+ # Custom ID parameter
805
+
806
+ TODO: Explain how idParam works, clarify that for the api it's always 'id' and there is no ID in the attributes
807
+
808
+ # Helpers and Methods Provided by REST API Plugins
809
+
810
+ The REST API plugins extend your API instance with various helpers and methods at different levels. Here's what becomes available:
811
+
812
+ ## API-Level Helpers
813
+
814
+ When you install the REST API plugins, the following helpers are added to `api.helpers`:
815
+
816
+ ### From RestApiPlugin
817
+
818
+ - **`api.helpers.getLocation(scopeName, id)`** - Generates the full URL for a resource
819
+ ```javascript
820
+ const url = api.helpers.getLocation('countries', '1');
821
+ // Returns: 'http://localhost:3000/api/countries/1'
822
+ ```
823
+
824
+ - **`api.helpers.getUrlPrefix(scope, context)`** - Gets the URL prefix for generating links
825
+ ```javascript
826
+ const prefix = api.helpers.getUrlPrefix(scope, context);
827
+ // Returns: 'http://localhost:3000/api'
828
+ ```
829
+
830
+ ### From RestApiKnexPlugin
831
+
832
+ - **`api.helpers.newTransaction()`** - Creates a new database transaction for atomic operations
833
+ ```javascript
834
+ const trx = await api.helpers.newTransaction();
835
+ try {
836
+ // Use transaction in multiple operations
837
+ await api.resources.countries.post({ name: 'France', code: 'FR' }, { transaction: trx });
838
+ await api.resources.publishers.post({ name: 'French Press', country_id: 1 }, { transaction: trx });
839
+ await trx.commit();
840
+ } catch (error) {
841
+ await trx.rollback();
842
+ throw error;
843
+ }
844
+ ```
845
+
846
+ ## API Namespaces
847
+
848
+ The plugins also create organized namespaces on the API instance:
849
+
850
+ ### `api.knex` Namespace (from RestApiKnexPlugin)
851
+
852
+ - **`api.knex.instance`** - Direct access to the Knex database instance
853
+ ```javascript
854
+ // Run raw queries when needed
855
+ const result = await api.knex.instance.raw('SELECT COUNT(*) FROM countries');
856
+ ```
857
+
858
+ - **`api.knex.capabilities`** - Information about database capabilities
859
+ ```javascript
860
+ console.log(api.knex.capabilities);
861
+ // { windowFunctions: true, dbInfo: { client: 'sqlite3', version: '3.36.0' } }
862
+ ```
863
+
864
+ ### `api.http` Namespace (from connector plugins)
865
+
866
+ When using ExpressPlugin:
867
+
868
+ - **`api.http.express.router`** - The Express router containing all API endpoints
869
+ - **`api.http.express.notFoundRouter`** - Express middleware for handling 404 errors
870
+
871
+ ```javascript
872
+ // In your Express app
873
+ app.use(api.http.express.router);
874
+ app.use(api.http.express.notFoundRouter);
875
+ ```
876
+
877
+ ## Resource-Level Methods
878
+
879
+ Each resource (added via `api.addResource()`) gets these methods automatically:
880
+
881
+ ### CRUD Operations
882
+
883
+ - **`api.resources.{resourceName}.query(params)`** - List resources with filtering, sorting, pagination
884
+ ```javascript
885
+ const countries = await api.resources.countries.query({
886
+ queryParams: {
887
+ filters: { name: 'United' },
888
+ sort: ['name'],
889
+ page: { size: 10, number: 1 }
890
+ }
891
+ });
892
+ ```
893
+
894
+ - **`api.resources.{resourceName}.get(params)`** - Retrieve a single resource by ID
895
+ ```javascript
896
+ const country = await api.resources.countries.get({ id: '1' });
897
+ ```
898
+
899
+ - **`api.resources.{resourceName}.post(params)`** - Create a new resource
900
+ ```javascript
901
+ const newCountry = await api.resources.countries.post({
902
+ name: 'Canada',
903
+ code: 'CA'
904
+ });
905
+ ```
906
+
907
+ - **`api.resources.{resourceName}.put(params)`** - Replace an entire resource
908
+ ```javascript
909
+ const updated = await api.resources.countries.put({
910
+ id: '1',
911
+ name: 'United States of America',
912
+ code: 'USA'
913
+ });
914
+ ```
915
+
916
+ - **`api.resources.{resourceName}.patch(params)`** - Partially update a resource
917
+ ```javascript
918
+ const patched = await api.resources.countries.patch({
919
+ id: '1',
920
+ name: 'USA'
921
+ });
922
+ ```
923
+
924
+ - **`api.resources.{resourceName}.delete(params)`** - Delete a resource
925
+ ```javascript
926
+ await api.resources.countries.delete({ id: '1' });
927
+ ```
928
+
929
+ ### Database Operations
930
+
931
+ - **`api.resources.{resourceName}.createKnexTable()`** - Creates the database table for this resource
932
+ ```javascript
933
+ // Create the table based on the schema definition
934
+ await api.resources.countries.createKnexTable();
935
+ ```
936
+
937
+
938
+ ## API Namespaces (internal, for plugin developers)
939
+
940
+ ### `api.rest` Namespace (from RestApiPlugin)
941
+
942
+ - **`api.rest.registerFileDetector(detector)`** - Registers file upload detectors (requires FileHandlingPlugin)
943
+ - **`api.rest.fileDetectors`** - Registry of file detectors for handling uploads
944
+ - **`api.knex.helpers.crossTableSearch`** - Utilities for searching across related tables
945
+
946
+ ## Summary
947
+
948
+ These helpers and methods provide a complete toolkit for:
949
+ - Building RESTful APIs with full CRUD support
950
+ - Managing database transactions
951
+ - Generating proper URLs and links
952
+ - Accessing the underlying database when needed
953
+ - Integrating with web frameworks like Express
954
+
955
+ The architecture ensures clean separation between HTTP transport, business logic, and data persistence layers.