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.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- 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.
|