json-rest-api 1.0.29 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) 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 +634 -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 +100 -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 +3827 -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 +36 -0
  44. package/index.js +1 -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 +64 -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 +213 -164
  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 +25 -16
  71. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  72. package/plugins/core/lib/dbIntrospection.js +228 -21
  73. package/plugins/core/lib/dbTablesOperations.js +409 -92
  74. package/plugins/core/lib/fractional-positioning.js +12 -48
  75. package/plugins/core/lib/querying/identifier-query.js +19 -0
  76. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  77. package/plugins/core/lib/querying/include-to-many.js +428 -0
  78. package/plugins/core/lib/querying/include-to-one.js +579 -0
  79. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  80. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  81. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  82. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  83. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  84. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  85. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  86. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  87. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  88. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  89. package/plugins/core/lib/querying/query-constraint.js +27 -0
  90. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  93. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  94. package/plugins/core/lib/querying/url-helpers.js +11 -4
  95. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  96. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  97. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  98. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  99. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  100. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  101. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  102. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  103. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  104. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  105. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  106. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  107. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  108. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  109. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  110. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  111. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  112. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  113. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  114. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  115. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  116. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  117. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  118. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  119. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  120. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  121. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  122. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  123. package/plugins/core/lib/writing/resource-version.js +172 -0
  124. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  125. package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
  126. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  127. package/plugins/core/rest-api-cors-plugin.js +52 -205
  128. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  129. package/plugins/core/rest-api-label-plugin.js +25 -38
  130. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  131. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  132. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  133. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  134. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  135. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  136. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  137. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  138. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  139. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  140. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  141. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  142. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  143. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  144. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  145. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  146. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  147. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  148. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  149. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  150. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  151. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  152. package/plugins/core/rest-api-plugin.js +32 -46
  153. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  154. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  155. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  156. package/plugins/core/socketio-plugin.js +364 -344
  157. package/plugins/storage/local-storage.js +40 -36
  158. package/types/autofilter.d.ts +57 -0
  159. package/types/bulk-methods.d.ts +63 -0
  160. package/types/errors.d.ts +118 -0
  161. package/types/fastify-options.d.ts +19 -0
  162. package/types/file-detectors.d.ts +33 -0
  163. package/types/file-storage.d.ts +48 -0
  164. package/types/hook-context.d.ts +180 -0
  165. package/types/plugin-options.d.ts +53 -0
  166. package/types/relationship-methods.d.ts +74 -0
  167. package/types/representations.d.ts +50 -0
  168. package/types/resource-methods.d.ts +95 -0
  169. package/types/resource-schema.d.ts +157 -0
  170. package/types/row-policy.d.ts +31 -0
  171. package/types/runtime.d.ts +132 -0
  172. package/types/transactions.d.ts +21 -0
  173. package/.claude/agents/library-guide-expert.md +0 -30
  174. package/.claude/agents/rest-api-internals-expert.md +0 -54
  175. package/.claude/settings.local.json +0 -70
  176. package/AGENTS.md +0 -34
  177. package/CLAUDE.md +0 -160
  178. package/CRUSH.md +0 -55
  179. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  180. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  181. package/TODO.todo +0 -13
  182. package/connectors_ideas/fastify.js +0 -217
  183. package/connectors_ideas/koa-plugin.js +0 -112
  184. package/docs/.bundle/config +0 -2
  185. package/docs/COMPARISON.md +0 -433
  186. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  187. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  188. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  189. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  190. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  191. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  192. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  193. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  194. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  195. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  196. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  197. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  198. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  199. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  200. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  201. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  202. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  203. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  204. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  205. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  206. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  207. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  208. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  209. package/docs/Gemfile +0 -6
  210. package/docs/Gemfile.lock +0 -308
  211. package/docs/ONBOARDING.md +0 -315
  212. package/docs/_config.yml +0 -36
  213. package/docs/_layouts/default.html +0 -123
  214. package/docs/apple-touch-icon.svg +0 -4
  215. package/docs/assets/css/style.scss +0 -392
  216. package/docs/favicon.svg +0 -4
  217. package/docs/index.md +0 -164
  218. package/docs/mini-guides/file-uploads-complete.md +0 -161
  219. package/eslint.config.js +0 -13
  220. package/examples/url-override-example.js +0 -137
  221. package/fixing_plan.md +0 -112
  222. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  223. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  224. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  225. package/plugins/storage/s3-storage.js +0 -93
  226. package/quickTest.js +0 -136
  227. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  228. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  229. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  230. package/scripts/run-docs.js +0 -133
  231. package/test-debug.js +0 -59
  232. package/tests/README.md +0 -202
  233. package/tests/TEST_TEMPLATE.test.js +0 -178
  234. package/tests/anyapi-basic.test.js +0 -105
  235. package/tests/anyapi-cursor-pagination.test.js +0 -148
  236. package/tests/anyapi-custom-idproperty.test.js +0 -163
  237. package/tests/autofilter.test.js +0 -463
  238. package/tests/bulk-operations.test.js +0 -657
  239. package/tests/computed-fields.test.js +0 -586
  240. package/tests/cors.test.js +0 -834
  241. package/tests/curl.test.js +0 -467
  242. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  243. package/tests/custom-id-simplified-post.test.js +0 -75
  244. package/tests/custom-idproperty-relationships.test.js +0 -932
  245. package/tests/db-introspection.test.js +0 -521
  246. package/tests/db-table-operations.test.js +0 -798
  247. package/tests/fastify-plugin.test.js +0 -184
  248. package/tests/field-getters.test.js +0 -370
  249. package/tests/field-setters.test.js +0 -306
  250. package/tests/file-handling.test.js +0 -155
  251. package/tests/fixtures/api-configs.js +0 -2566
  252. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  253. package/tests/helpers/fake-fastify.js +0 -99
  254. package/tests/helpers/storage-mode.js +0 -65
  255. package/tests/helpers/test-utils.js +0 -203
  256. package/tests/http-connectors-parity.test.js +0 -395
  257. package/tests/id-normalization.test.js +0 -444
  258. package/tests/include-depth-validation.test.js +0 -331
  259. package/tests/includes.test.js +0 -427
  260. package/tests/local-storage.test.js +0 -96
  261. package/tests/logical-id-write-validation.test.js +0 -151
  262. package/tests/nested-includes.test.js +0 -617
  263. package/tests/pagination-boundaries.test.js +0 -154
  264. package/tests/pagination-contract.test.js +0 -122
  265. package/tests/pagination-cursor-multifield.test.js +0 -641
  266. package/tests/pagination-enhanced.test.js +0 -563
  267. package/tests/polymorphic-relationships.test.js +0 -347
  268. package/tests/polymorphic-simple.test.js +0 -148
  269. package/tests/positioning-advanced.test.js +0 -692
  270. package/tests/positioning-extended.test.js +0 -915
  271. package/tests/positioning-multifilter.test.js +0 -699
  272. package/tests/positioning.test.js +0 -560
  273. package/tests/post-commit-hooks.test.js +0 -120
  274. package/tests/projected-fields.test.js +0 -319
  275. package/tests/public-exports.test.js +0 -15
  276. package/tests/put-create-parity.test.js +0 -216
  277. package/tests/queries.test.js +0 -521
  278. package/tests/query-limits.test.js +0 -584
  279. package/tests/relationship-endpoints.test.js +0 -620
  280. package/tests/relationship-include-storage-adapter.test.js +0 -56
  281. package/tests/relationships.test.js +0 -681
  282. package/tests/rest-api.test.js +0 -337
  283. package/tests/return-record-settings.test.js +0 -184
  284. package/tests/row-policy.test.js +0 -489
  285. package/tests/s3-storage.test.js +0 -38
  286. package/tests/searchschema-knex-behavior.test.js +0 -187
  287. package/tests/searchschema-merge.test.js +0 -151
  288. package/tests/socketio.test.js +0 -860
  289. package/tests/storage-mapping.test.js +0 -362
  290. package/tests/temporal-boundaries.test.js +0 -208
  291. package/tests/test-development-guide.md +0 -370
  292. package/tests/virtual-fields.test.js +0 -404
@@ -1,1057 +0,0 @@
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' });
45
-
46
- // Install plugins
47
- await api.use(RestApiPlugin);
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: { type: 'hasMany', target: 'publishers', foreignKey: 'country_id' },
60
- books: { type: 'hasMany', target: '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
- logLevel: 'warn' // Only show warnings and errors
93
- });
94
- ```
95
-
96
- By default, the INFO level logs you're seeing are the default. To reduce them, you could use:
97
-
98
- - logLevel: `warn` - Only see warnings and errors
99
- - logLevel: `error` - Only see errors
100
- - logLevel: `silent` - No logs at all
101
-
102
- To see more detail for debugging:
103
-
104
- - logLevel: `debug` - More detailed information
105
- - logLevel: `trace` - Everything, including hook executions and internal operations
106
-
107
- ### Database Options
108
-
109
- The `json-rest-knex-plugin` plugin uses `knex` as its database abstraction layer, which supports a wide variety of SQL databases.
110
- In the example above, we configured `knex` to use an in-memory SQLite database for simplicity:
111
-
112
- ```javascript
113
- const knex = knexLib({
114
- client: 'sqlite3',
115
- connection: {
116
- filename: ':memory:' // In-memory database for quick examples
117
- },
118
- useNullAsDefault: true // Recommended for SQLite
119
- });
120
- ```
121
-
122
- 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:
123
-
124
- **PostgreSQL:**
125
-
126
- ```javascript
127
- const knex = knexLib({
128
- client: 'pg', // PostgreSQL client
129
- connection: {
130
- host: '127.0.0.1',
131
- user: 'your_username',
132
- password: 'your_password',
133
- database: 'your_database_name',
134
- port: 5432 // Default PostgreSQL port
135
- }
136
- });
137
- ```
138
- **MySQL / MariaDB:**
139
-
140
- ```javascript
141
- const knex = knexLib({
142
- client: 'mysql', // or 'mariasql' for MariaDB
143
- connection: {
144
- host: '127.0.0.1',
145
- user: 'your_username',
146
- password: 'your_password',
147
- database: 'your_database_name',
148
- port: 3306 // Default MySQL/MariaDB port
149
- }
150
- });
151
- ```
152
-
153
- 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.
154
-
155
- ### Programmatic Usage
156
-
157
- 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.
158
-
159
- The instanced object becomes a fully-fledged, database and schema aware API.
160
-
161
- Once your resources are defined using `api.addResource()`, you can directly call CRUD (Create, Read, Update, Delete) methods on `api.resources.<resourceName>`.
162
-
163
- Let's start by creating a `country` record:
164
-
165
- ```javascript
166
- // Example: Create a country
167
- const countryUs = await api.resources.countries.post({
168
- name: 'United States',
169
- code: 'US'
170
- });
171
- console.log('Created Country:', inspect(countryUs));
172
- // Expected Output:
173
- // Created Country: { id: '1', name: 'United States', code: 'US' }
174
- ```
175
-
176
- Now, let's retrieve this country data using its ID:
177
-
178
- ```javascript
179
- // Example: Refetch a country by ID
180
- const countryUsRefetched = await api.resources.countries.get({
181
- id: countryUs.id, // Use the ID returned from the POST operation
182
- });
183
- console.log('Refetched Country:', inspect(countryUsRefetched));
184
- // Expected Output:
185
- // Refetched Country: { id: '1', name: 'United States', code: 'US' }
186
- ```
187
-
188
- The database is populated, and the newly added record is then fetched.
189
-
190
- #### API usage and simplified mode
191
-
192
- 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.
193
-
194
- Simplified mode changes:
195
-
196
- - **Input**: You can pass plain objects with just the attributes
197
- - **Output**: You get back plain objects with id and attributes merged at the top level
198
-
199
- Here's how the same operations look when **NOT** using simplified mode:
200
-
201
- ```javascript
202
- // Create a country (non-simplified mode)
203
- const countryUs = await api.resources.countries.post({
204
- inputRecord: {
205
- data: {
206
- type: 'countries',
207
- attributes: {
208
- name: 'United States',
209
- code: 'US'
210
- }
211
- }
212
- },
213
- simplified: false
214
- });
215
- console.log('Created Country:', inspect(countryUs));
216
- // Expected Output:
217
- // Created Country: {
218
- // data: {
219
- // type: 'countries',
220
- // id: '1',
221
- // attributes: { name: 'United States', code: 'US' },
222
- // links: { self: '/api/countries/1' }
223
- // },
224
- // links: { self: '/api/countries/1' }
225
- // }
226
-
227
- // Fetch a country by ID (non-simplified mode)
228
- const countryUsRefetched = await api.resources.countries.get({
229
- id: countryUs.data.id,
230
- simplified: false
231
- });
232
- console.log('Refetched Country:', inspect(countryUsRefetched));
233
- // Expected Output (a full JSON:API record):
234
- // Refetched Country: {
235
- // data: {
236
- // type: 'countries',
237
- // id: '1',
238
- // attributes: { name: 'United States', code: 'US' },
239
- // links: { self: '/api/countries/1' }
240
- // },
241
- // links: { self: '/api/countries/1' }
242
- // }
243
- ```
244
-
245
- (Note that the full JSON:API record includes links to resources, which are generated from explicit URL configuration such as the connector `mountPath` or `publicBaseUrl`.)
246
-
247
- As you can see, when `simplified: false` is used:
248
-
249
- - Input requires the full JSON:API document structure with `data`, `type`, and `attributes`
250
- - Output returns the full JSON:API response with the same nested structure (and links)
251
- - You need to access the ID as `result.data.id` instead of just `result.id`
252
-
253
- **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.
254
-
255
- For example:
256
-
257
- 1. **Global default**: Set during plugin installation
258
- ```javascript
259
- await api.use(RestApiPlugin, {
260
- simplifiedApi: false, // All API calls will use JSON:API format by default
261
- simplifiedTransport: true // All HTTP calls will use simplified format by default
262
- });
263
- ```
264
-
265
- 2. **Per-resource override**: Set when defining a resource
266
- ```javascript
267
- await api.addResource('countries', {
268
- schema: {
269
- name: { type: 'string', required: true },
270
- code: { type: 'string', required: true }
271
- },
272
- simplifiedApi: false, // API calls to this resource use JSON:API format
273
- simplifiedTransport: true // HTTP calls to this resource use simplified format
274
- });
275
- ```
276
-
277
- NOTE: this can also be written as:
278
-
279
- ```javascript
280
- await api.addResource('countries', {
281
- schema: {
282
- name: { type: 'string', required: true },
283
- code: { type: 'string', required: true }
284
- },
285
-
286
- },{
287
- // Parameters set directly into 'vars'
288
- vars: {
289
- simplifiedApi: false, // API calls to this resource use JSON:API format
290
- simplifiedTransport: true // HTTP calls to this resource use simplified format
291
- }
292
- }
293
- );
294
- ```
295
-
296
-
297
- 3. **Per-call override**: Set in individual method calls
298
- ```javascript
299
- // Force non-simplified for this call only
300
- const result = await api.resources.countries.post({
301
- inputRecord: {
302
- data: {
303
- type: 'countries',
304
- attributes: {
305
- name: 'United States',
306
- code: 'US'
307
- }
308
- }
309
- },
310
- simplified: false
311
- });
312
-
313
- ```
314
-
315
- The hierarchy is: **per-call → per-resource (parameters or variables) → global default**
316
-
317
- **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.
318
-
319
- **Special case**: When passing attributes directly (without `inputRecord`), simplified mode is always `true` regardless of configuration:
320
- ```javascript
321
- // This ALWAYS uses simplified mode, even if global/resource setting is false
322
- const result = await api.resources.countries.post({
323
- name: 'United States',
324
- code: 'US'
325
- });
326
- ```
327
-
328
- If you need per-call options such as `transaction`, `returnFullRecord`, or `queryParams`, move the record under `inputRecord` and keep those options at the top level of the params object.
329
-
330
- 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.
331
-
332
- #### API usage and returning records
333
-
334
- 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.
335
-
336
- There are TWO separate settings for this:
337
-
338
- 1. **`returnRecordApi`** - Controls what **programmatic API calls** return (default: `'full'`)
339
- 2. **`returnRecordTransport`** - Controls what **HTTP/REST endpoints** return (default: `'no'`)
340
-
341
- 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.
342
-
343
- Both settings prefer three string values:
344
- - **`'full'`**: Returns the complete record with all attributes, relationships, computed fields, and links
345
- - **`'minimal'`**: Returns only the resource type and ID
346
- - **`'no'`**: Returns nothing (undefined in programmatic calls, 204 No Content in HTTP)
347
-
348
- Boolean values are accepted for backward compatibility: `true` is normalized to `'full'` and `false` is normalized to `'no'`. New code should use the string values.
349
-
350
- Here's how these settings work:
351
-
352
- ```javascript
353
- // Example 1: Using defaults
354
- const api = new Api({ name: 'api' });
355
- await api.use(RestApiPlugin);
356
- // Default: returnRecordApi='full', returnRecordTransport='no'
357
-
358
- // Programmatic API call returns full record by default
359
- const country = await api.resources.countries.post({
360
- name: 'Canada',
361
- code: 'CA'
362
- });
363
- console.log('API result:', country);
364
- // Expected Output:
365
- // API result: { id: '1', name: 'Canada', code: 'CA' }
366
-
367
- // But the same operation via HTTP returns 204 No Content by default
368
- // POST /api/countries -> 204 No Content (no body)
369
-
370
- // Example 2: Different settings for API and Transport
371
- await api.use(RestApiPlugin, {
372
- returnRecordApi: 'minimal', // API calls return minimal
373
- returnRecordTransport: 'full' // HTTP calls return full
374
- });
375
-
376
- // API call returns minimal
377
- const apiResult = await api.resources.countries.post({
378
- name: 'Mexico',
379
- code: 'MX'
380
- });
381
- console.log('API result:', apiResult);
382
- // Expected Output:
383
- // API result: { id: '2', type: 'countries' }
384
-
385
- // HTTP call returns full record
386
- // POST /api/countries -> 204 No Content
387
- // Body: { data: { type: 'countries', id: '3', attributes: { name: 'Mexico', code: 'MX' } } }
388
-
389
- // Example 3: Per-method configuration
390
- await api.use(RestApiPlugin, {
391
- returnRecordApi: {
392
- post: 'full', // API POST returns full
393
- put: 'minimal', // API PUT returns minimal
394
- patch: 'no' // API PATCH returns nothing
395
- },
396
- returnRecordTransport: {
397
- post: 'minimal', // HTTP POST returns minimal
398
- put: 'no', // HTTP PUT returns 204
399
- patch: 'full' // HTTP PATCH returns full
400
- }
401
- });
402
- ```
403
-
404
- When combined with non-simplified mode, the difference is even more apparent:
405
-
406
- ```javascript
407
- // Non-simplified mode with full record
408
- const fullJsonApi = await api.resources.countries.post({
409
- inputRecord: {
410
- data: {
411
- type: 'countries',
412
- attributes: { name: 'France', code: 'FR' }
413
- }
414
- },
415
- simplified: false
416
- });
417
- console.log('Full JSON:API response:', inspect(fullJsonApi));
418
- // Expected Output:
419
- // Full JSON:API response: {
420
- // data: {
421
- // type: 'countries',
422
- // id: '4',
423
- // attributes: { name: 'France', code: 'FR' },
424
- // links: { self: '/api/countries/4' }
425
- // },
426
- // links: { self: '/api/countries/4' }
427
- // }
428
-
429
- // Non-simplified mode with minimal return
430
- const minimalJsonApi = await api.resources.countries.post({
431
- inputRecord: {
432
- data: {
433
- type: 'countries',
434
- attributes: { name: 'Germany', code: 'DE' }
435
- }
436
- },
437
- simplified: false
438
- });
439
- console.log('Minimal JSON:API response:', inspect(minimalJsonApi));
440
- // Expected Output:
441
- // Minimal JSON:API response: { id: '5', type: 'countries' }
442
- ```
443
-
444
- **Configuration Levels**: Both `returnRecordApi` and `returnRecordTransport` can be configured at multiple levels, with the hierarchy being: per-call → per-resource → global default.
445
-
446
- **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.
447
-
448
- For example:
449
-
450
- 1. **Global default**: Set during plugin installation
451
- ```javascript
452
- await api.use(RestApiPlugin, {
453
- returnRecordApi: {
454
- post: 'full', // API POST returns full
455
- put: 'minimal', // API PUT returns minimal
456
- patch: 'full' // API PATCH returns full
457
- },
458
- returnRecordTransport: {
459
- post: 'minimal', // HTTP POST returns minimal
460
- put: 'no', // HTTP PUT returns 204
461
- patch: 'minimal' // HTTP PATCH returns minimal
462
- }
463
- });
464
- ```
465
-
466
- 2. **Per-resource override**: Set when defining a resource
467
- ```javascript
468
- await api.addResource('countries', {
469
- schema: {
470
- name: { type: 'string', required: true },
471
- code: { type: 'string', required: true }
472
- },
473
- returnRecordApi: 'full', // All API methods return full
474
- returnRecordTransport: 'minimal' // All HTTP methods return minimal
475
- });
476
-
477
- // Or with per-method granularity:
478
- await api.addResource('products', {
479
- schema: {
480
- name: { type: 'string', required: true },
481
- price: { type: 'number', required: true }
482
- },
483
- returnRecordApi: {
484
- post: 'full', // API POST returns full record
485
- put: 'minimal', // API PUT returns minimal
486
- patch: 'no' // API PATCH returns nothing
487
- },
488
- returnRecordTransport: {
489
- post: 'minimal', // HTTP POST returns minimal
490
- put: 'no', // HTTP PUT returns 204
491
- patch: 'full' // HTTP PATCH returns full record
492
- }
493
- });
494
- ```
495
-
496
- 3. **Per-call override**: Set in individual method calls
497
- ```javascript
498
- // Override for a specific API call
499
- const result = await api.resources.countries.patch({
500
- inputRecord: {
501
- id: '1',
502
- name: 'United States of America'
503
- },
504
- returnFullRecord: 'minimal' // Overrides the configured setting
505
- });
506
- // result = { id: '1', type: 'countries' }
507
- ```
508
-
509
- **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.
510
-
511
- **Remember the defaults**:
512
- - `returnRecordApi` defaults to `'full'` (convenient for development)
513
- - `returnRecordTransport` defaults to `'no'` (optimal for performance)
514
-
515
- ### REST Usage (HTTP Endpoints)
516
-
517
- Since this is a REST API, its main purpose is to be used with a REST interface over HTTP.
518
- To expose your API resources via HTTP, you need to install one of the connector plugins:
519
-
520
- * **`ExpressPlugin`**: If you are using `Express.js` in your application.
521
-
522
- * **`(Coming soon)`**: Fastify and Koa are planned and coming soon
523
-
524
- Thanks to the ExpressPlugin, `json-rest-api` is able to export an Express router that you can just `use()` in Express.
525
-
526
- Just modify the example above so that it looks like this:
527
-
528
- ```javascript
529
- import { RestApiPlugin, RestApiKnexPlugin, ExpressPlugin } from 'json-rest-api'; // Added: ExpressPlugin
530
- import { Api } from 'hooked-api';
531
- import knexLib from 'knex';
532
- import util from 'util';
533
- import express from 'express'; // Added: Express
534
-
535
- // Utility used throughout this guide
536
- const inspect = (obj) => util.inspect(obj, { depth: 5 })
537
-
538
- // Create a Knex instance connected to SQLite in-memory database
539
- const knex = knexLib({
540
- client: 'better-sqlite3',
541
- connection: {
542
- filename: ':memory:'
543
- },
544
- useNullAsDefault: true
545
- });
546
-
547
- // Create API instance
548
- const api = new Api({ name: 'book-catalog-api' });
549
-
550
- // Install plugins
551
- await api.use(RestApiPlugin);
552
- await api.use(RestApiKnexPlugin, { knex });
553
- await api.use(ExpressPlugin, { mountPath: '/api' }); // Added: Express Plugin
554
-
555
- // Countries table
556
- await api.addResource('countries', {
557
- schema: {
558
- name: { type: 'string', required: true, max: 100, search: true },
559
- code: { type: 'string', max: 2, unique: true, search: true }, // ISO country code
560
- }
561
- });
562
- await api.resources.countries.createKnexTable()
563
-
564
- /// *** ...programmatic calls here... ***
565
-
566
- // Create the express server and add the API's routes
567
- const app = express();
568
- app.use(api.http.express.router);
569
- app.use(api.http.express.notFoundRouter);
570
-
571
- app.listen(3000, () => {
572
- console.log('Express server started on port 3000. API available at http://localhost:3000/api');
573
- }).on('error', (err) => {
574
- console.error('Failed to start server:', err);
575
- process.exit(1)
576
- });
577
-
578
- // Close the database connection // no longer happening since the server stays on
579
- // await knex.destroy();
580
- // console.log('\n✅ All schemas created successfully!');
581
- // console.log('Database connection closed.');
582
- ```
583
-
584
- Since you added `express`, you will need to install it:
585
-
586
- ```bash
587
- npm install express
588
- ```
589
-
590
- 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
591
-
592
- Once the server is running, you can interact with your API using tools like `curl`.
593
-
594
- **REST Example: Create a Country**
595
-
596
- ```bash
597
- curl -i -X POST -H "Content-Type: application/vnd.api+json" \
598
- -d '{
599
- "data": {
600
- "type": "countries",
601
- "attributes": {
602
- "name": "United Kingdom",
603
- "code": "UK"
604
- }
605
- }
606
- }' http://localhost:3000/api/countries
607
- ```
608
-
609
- This will have no response (204 No Content) since by default resources won't return anything when using HTTP:
610
-
611
- ```
612
- HTTP/1.1 204 No Content
613
- X-Powered-By: Express
614
- Location: http://localhost:3000/api/countries/1
615
- ETag: W/"a-bAsFyilMr4Ra1hIU5PyoyFRunpI"
616
- Date: Tue, 22 Jul 2025 14:54:45 GMT
617
- Connection: keep-alive
618
- Keep-Alive: timeout=5
619
- ```
620
-
621
- **REST Example: Get a Country by ID**
622
-
623
- ```bash
624
- curl -X GET http://localhost:3000/api/countries/2
625
- ```
626
-
627
- The result:
628
-
629
- ```json
630
- {
631
- "data": {
632
- "type": "countries",
633
- "id": "1",
634
- "attributes": {
635
- "name": "United Kingdom",
636
- "code": "UK"
637
- },
638
- "links": {
639
- "self": "http://localhost:3000/api/countries/1"
640
- }
641
- },
642
- "links": {
643
- "self": "http://localhost:3000/api/countries/1"
644
- }
645
- }
646
- ```
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, { returnRecordTransport: 'minimal' });
764
-
765
- // ...or...
766
-
767
- await api.use(RestApiPlugin, {
768
- vars: {
769
- returnRecordTransport: 'minimal'
770
- }
771
- });
772
- ```
773
-
774
- Here is a full list of parameters and their respective variables:
775
-
776
- | Parameter | Variable Name | Default Value | Description | Scope Override |
777
- |-----------|--------------|---------------|-------------|----------------|
778
- | `queryDefaultLimit` | `vars.queryDefaultLimit` | `25` | Default number of records returned in query results | ✓ |
779
- | `queryMaxLimit` | `vars.queryMaxLimit` | `100` | Maximum allowed limit for query results | ✓ |
780
- | `includeDepthLimit` | `vars.includeDepthLimit` | `3` | Maximum depth for nested relationship includes | ✓ |
781
- | `enablePaginationCounts` | `vars.enablePaginationCounts` | `true` | Whether to include total count in pagination metadata | ✓ |
782
- | `simplifiedApi` | `vars.simplifiedApi` | `true` | Use simplified format for programmatic API calls | ✓ |
783
- | `simplifiedTransport` | `vars.simplifiedTransport` | `false` | Use simplified format for HTTP/REST endpoints | ✓ |
784
- | `idProperty` | `vars.idProperty` | `'id'` | Name of the ID field in resources | ✓ |
785
- | `normalizeId` | `vars.normalizeId` | trim strings, stringify finite numbers | Normalize resource identifiers before lookups and writes | ✓ |
786
- | `returnRecordApi` | `vars.returnRecordApi` | `{ post: 'full', put: 'full', patch: 'full' }` | What to return for programmatic API write operations | ✓ |
787
- | `returnRecordTransport` | `vars.returnRecordTransport` | `{ post: 'no', put: 'no', patch: 'no' }` | What to return for HTTP/REST write operations | ✓ |
788
-
789
- **Resource-specific parameters** (only available at resource level, not plugin level):
790
-
791
- | Parameter | Variable Name | Default Value | Description |
792
- |-----------|--------------|---------------|-------------|
793
- | `sortableFields` | `vars.sortableFields` | `[]` | Array of field names that can be used for sorting |
794
- | `defaultSort` | `vars.defaultSort` | `null` | Default sort order for queries (e.g., `['-createdAt', 'name']`) |
795
-
796
- **Notes:**
797
- - "Scope Override" indicates whether the parameter can be overridden at the resource (scope) level
798
- - `returnRecordApi` and `returnRecordTransport` can be either:
799
- - A string: `'no'`, `'minimal'`, or `'full'` (applies to all methods)
800
- - An object: `{ post: 'full', put: 'minimal', patch: 'no' }` (per-method configuration)
801
- - Legacy booleans: `true` is treated as `'full'`, `false` as `'no'`
802
- - All parameters support the cascade: per-call → resource-level → plugin-level default
803
-
804
- # Logical IDs and Physical Columns
805
-
806
- For table-backed resources, `idProperty` defines the physical primary-key column. The API surface still uses the logical resource id.
807
-
808
- - simplified writes use `inputRecord.id`
809
- - JSON:API writes use `inputRecord.data.id`
810
- - responses expose `id` or `data.id`
811
- - the resource id is not part of `attributes`
812
-
813
- ```javascript
814
- await api.addResource('profiles', {
815
- idProperty: 'user_id',
816
- schema: {
817
- id: { type: 'id', required: true, storage: { column: 'user_id' } },
818
- displayName: { type: 'string', required: true },
819
- loginCount: { type: 'number', defaultTo: 0 }
820
- },
821
- tableName: 'profiles'
822
- });
823
- ```
824
-
825
- For table-backed resources, storage columns default to snake_case, so `displayName` maps to `display_name` and `loginCount` maps to `login_count`. Use `storage.column` for a per-field override, or `storage: { naming: 'exact' }` when physical column names should match logical field names exactly.
826
-
827
- ## ID normalization
828
-
829
- `normalizeId` lets you canonicalize resource identifiers before the library reads, writes, validates, or links records.
830
-
831
- The default normalizer already handles the common cases:
832
-
833
- - trims surrounding whitespace from strings
834
- - converts finite numbers and bigints to strings
835
- - rejects values that normalize to an empty identifier
836
-
837
- You only need to provide `normalizeId` when your resource ids need additional canonicalization, such as uppercasing, lowercasing, or removing formatting characters.
838
-
839
- ```javascript
840
- await api.use(RestApiPlugin, {
841
- normalizeId: (value) => {
842
- if (value === null || value === undefined) return null
843
-
844
- const normalized = String(value).trim()
845
- return normalized ? normalized.toUpperCase() : null
846
- }
847
- })
848
-
849
- await api.addResource('countries', {
850
- // Override the plugin-wide normalizer for this one resource
851
- normalizeId: (value) => {
852
- if (value === null || value === undefined) return null
853
-
854
- const normalized = String(value).trim()
855
- return normalized ? normalized.toLowerCase() : null
856
- },
857
- schema: {
858
- name: { type: 'string', required: true }
859
- }
860
- })
861
- ```
862
-
863
- Normalization is applied consistently to:
864
-
865
- - `get`, `put`, `patch`, `delete`, `getRelated`, and `getRelationship` parent ids
866
- - explicit resource ids in `POST`, `PUT`, and `PATCH` documents
867
- - relationship identifiers supplied to `postRelationship`, `patchRelationship`, and `deleteRelationship`
868
-
869
- The resource-level `normalizeId` override also applies to relationship identifiers when that resource appears as the related target.
870
-
871
- If an id normalizes to an empty value:
872
-
873
- - existing-resource operations behave as `not_found`
874
- - explicit `POST` document ids are rejected as validation errors on `data.id`
875
-
876
- For full examples of table creation, physical column naming, and migration generation, see [Knex Schema and Migrations](GUIDE_X_Knex_Schema_And_Migrations.md).
877
-
878
- # Helpers and Methods Provided by REST API Plugins
879
-
880
- The REST API plugins extend your API instance with various helpers and methods at different levels. Here's what becomes available:
881
-
882
- ## API-Level Helpers
883
-
884
- When you install the REST API plugins, the following helpers are added to `api.helpers`:
885
-
886
- ### From RestApiPlugin
887
-
888
- - **`api.helpers.getLocation(scopeName, id)`** - Generates the full URL for a resource
889
- ```javascript
890
- const url = api.helpers.getLocation('countries', '1');
891
- // Returns: 'http://localhost:3000/api/countries/1'
892
- ```
893
-
894
- - **`api.helpers.getUrlPrefix(scope, context)`** - Gets the URL prefix for generating links
895
- ```javascript
896
- const prefix = api.helpers.getUrlPrefix(scope, context);
897
- // Returns: 'http://localhost:3000/api'
898
- ```
899
-
900
- ### From RestApiKnexPlugin
901
-
902
- - **`api.helpers.newTransaction()`** - Creates a new database transaction for atomic operations
903
- ```javascript
904
- const trx = await api.helpers.newTransaction();
905
- try {
906
- // Use transaction in multiple operations
907
- await api.resources.countries.post({
908
- inputRecord: { name: 'France', code: 'FR' },
909
- transaction: trx
910
- });
911
- await api.resources.publishers.post({
912
- inputRecord: { name: 'French Press', country: 1 },
913
- transaction: trx
914
- });
915
- await trx.commit();
916
- } catch (error) {
917
- await trx.rollback();
918
- throw error;
919
- }
920
- ```
921
-
922
- ## API Namespaces
923
-
924
- The plugins also create organized namespaces on the API instance:
925
-
926
- ### `api.knex` Namespace (from RestApiKnexPlugin)
927
-
928
- - **`api.knex.instance`** - Direct access to the Knex database instance
929
- ```javascript
930
- // Run raw queries when needed
931
- const result = await api.knex.instance.raw('SELECT COUNT(*) FROM countries');
932
- ```
933
-
934
- - **`api.knex.capabilities`** - Information about database capabilities
935
- ```javascript
936
- console.log(api.knex.capabilities);
937
- // { windowFunctions: true, dbInfo: { client: 'sqlite3', version: '3.36.0' } }
938
- ```
939
-
940
- ### `api.http` Namespace (from connector plugins)
941
-
942
- When using ExpressPlugin:
943
-
944
- - **`api.http.express.router`** - The Express router containing all API endpoints
945
- - **`api.http.express.notFoundRouter`** - Express middleware for handling 404 errors
946
-
947
- ```javascript
948
- // In your Express app
949
- app.use(api.http.express.router);
950
- app.use(api.http.express.notFoundRouter);
951
- ```
952
-
953
- ## Resource-Level Methods
954
-
955
- Each resource (added via `api.addResource()`) gets these methods automatically:
956
-
957
- ### CRUD Operations
958
-
959
- - **`api.resources.{resourceName}.query(params)`** - List resources with filtering, sorting, pagination
960
- ```javascript
961
- const countries = await api.resources.countries.query({
962
- queryParams: {
963
- filters: { name: 'United' },
964
- sort: ['name'],
965
- page: { size: 10, number: 1 }
966
- }
967
- });
968
- ```
969
-
970
- - **`api.resources.{resourceName}.get(params)`** - Retrieve a single resource by ID
971
- ```javascript
972
- const country = await api.resources.countries.get({ id: '1' });
973
- ```
974
-
975
- - **`api.resources.{resourceName}.post(params)`** - Create a new resource
976
- ```javascript
977
- const newCountry = await api.resources.countries.post({
978
- name: 'Canada',
979
- code: 'CA'
980
- });
981
- ```
982
-
983
- - **`api.resources.{resourceName}.put(params)`** - Replace an entire resource
984
- ```javascript
985
- const updated = await api.resources.countries.put({
986
- id: '1',
987
- name: 'United States of America',
988
- code: 'USA'
989
- });
990
- ```
991
-
992
- - **`api.resources.{resourceName}.patch(params)`** - Partially update a resource
993
- ```javascript
994
- const patched = await api.resources.countries.patch({
995
- id: '1',
996
- name: 'USA'
997
- });
998
- ```
999
-
1000
- - **`api.resources.{resourceName}.delete(params)`** - Delete a resource
1001
- ```javascript
1002
- await api.resources.countries.delete({ id: '1' });
1003
- ```
1004
-
1005
- ### Database Operations
1006
-
1007
- - **`api.resources.{resourceName}.createKnexTable()`** - Creates the database table for this resource
1008
- ```javascript
1009
- // Create the table based on the schema definition
1010
- await api.resources.countries.createKnexTable();
1011
- ```
1012
-
1013
- - **`api.resources.{resourceName}.introspectKnexTableSnapshot()`** - Returns a normalized snapshot of the live physical table
1014
- ```javascript
1015
- const snapshot = await api.resources.countries.introspectKnexTableSnapshot();
1016
- ```
1017
-
1018
- - **`api.resources.{resourceName}.generateKnexMigration()`** - Generates a Knex create migration string for the resource
1019
- ```javascript
1020
- const migration = await api.resources.countries.generateKnexMigration();
1021
- ```
1022
-
1023
- - **`api.resources.{resourceName}.generateKnexMigrationDiff()`** - Generates an additive diff migration from the live table snapshot
1024
- ```javascript
1025
- const diff = await api.resources.countries.generateKnexMigrationDiff();
1026
- ```
1027
-
1028
- - **`api.resources.{resourceName}.addKnexFields({ fields })`** / **`alterKnexFields({ fields })`** - Column-only helpers for existing tables
1029
- ```javascript
1030
- await api.resources.countries.addKnexFields({
1031
- fields: {
1032
- iso3: { type: 'string', maxLength: 3 }
1033
- }
1034
- });
1035
- ```
1036
-
1037
- For full examples of snapshots, migration generation, diffing, and table metadata such as indexes / foreign keys / checks, see [Knex Schema and Migrations](GUIDE_X_Knex_Schema_And_Migrations.md).
1038
-
1039
-
1040
- ## API Namespaces (internal, for plugin developers)
1041
-
1042
- ### `api.rest` Namespace (from RestApiPlugin)
1043
-
1044
- - **`api.rest.registerFileDetector(detector)`** - Registers file upload detectors (requires FileHandlingPlugin)
1045
- - **`api.rest.fileDetectors`** - Registry of file detectors for handling uploads
1046
-
1047
- ## Summary
1048
-
1049
- These helpers and methods provide a complete toolkit for:
1050
- - Building RESTful APIs with full CRUD support
1051
- - Managing database transactions
1052
- - Generating proper URLs and links
1053
- - Accessing the underlying database when needed
1054
- - Inspecting live tables and generating Knex migrations when using table-backed resources
1055
- - Integrating with web frameworks like Express
1056
-
1057
- The architecture ensures clean separation between HTTP transport, business logic, and data persistence layers.