json-rest-api 1.0.29 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +733 -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 +103 -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 +3838 -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 +37 -0
- package/index.js +3 -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 +70 -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 +586 -418
- 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 +120 -56
- package/plugins/core/lib/anyapi/stored-definition.js +109 -0
- 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 +290 -2475
- 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/anyapi.d.ts +30 -0
- 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 +65 -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,517 +0,0 @@
|
|
|
1
|
-
# REST API Relationships Plugin Guide
|
|
2
|
-
|
|
3
|
-
The REST API Relationships Plugin adds JSON:API compliant relationship endpoints to your API, allowing clients to view and manage relationships between resources as first-class citizens. This guide shows you how to use these powerful features with our book catalog system.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
- [Why Use Relationship Endpoints?](#why-use-relationship-endpoints)
|
|
7
|
-
- [Installation](#installation)
|
|
8
|
-
- [Understanding Relationship Endpoints](#understanding-relationship-endpoints)
|
|
9
|
-
- [Working with Relationships](#working-with-relationships)
|
|
10
|
-
- [Viewing Relationship Links](#viewing-relationship-links)
|
|
11
|
-
- [Fetching Related Resources](#fetching-related-resources)
|
|
12
|
-
- [Adding Relationships](#adding-relationships)
|
|
13
|
-
- [Replacing Relationships](#replacing-relationships)
|
|
14
|
-
- [Removing Relationships](#removing-relationships)
|
|
15
|
-
- [Relationship Types](#relationship-types)
|
|
16
|
-
- [Security and Permissions](#security-and-permissions)
|
|
17
|
-
- [Error Handling](#error-handling)
|
|
18
|
-
- [Best Practices](#best-practices)
|
|
19
|
-
|
|
20
|
-
## Why Use Relationship Endpoints?
|
|
21
|
-
|
|
22
|
-
Relationship endpoints provide several advantages:
|
|
23
|
-
|
|
24
|
-
1. **Efficient Relationship Management**: Add or remove related items without fetching and updating entire resources
|
|
25
|
-
2. **Clear API Navigation**: Self-documenting links show how resources connect
|
|
26
|
-
3. **Reduced Payload Size**: Fetch just relationship data without full resource details
|
|
27
|
-
4. **Atomic Operations**: Manage relationships in isolation with proper transaction support
|
|
28
|
-
|
|
29
|
-
## Installation
|
|
30
|
-
|
|
31
|
-
To use relationship endpoints, install the plugin after your core REST API setup:
|
|
32
|
-
|
|
33
|
-
```javascript
|
|
34
|
-
import { RestApiPlugin, RestApiKnexPlugin } from 'json-rest-api';
|
|
35
|
-
import { Api } from 'hooked-api';
|
|
36
|
-
|
|
37
|
-
const api = new Api();
|
|
38
|
-
|
|
39
|
-
// Core plugins first
|
|
40
|
-
await api.use(RestApiPlugin, {
|
|
41
|
-
resourceUrlPrefix: '/api' // Important: enables relationship links
|
|
42
|
-
});
|
|
43
|
-
await api.use(RestApiKnexPlugin, { knex });
|
|
44
|
-
|
|
45
|
-
// Define your resources as usual
|
|
46
|
-
await api.addResource('books', { /* schema */ });
|
|
47
|
-
await api.addResource('authors', { /* schema */ });
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Understanding Relationship Endpoints
|
|
51
|
-
|
|
52
|
-
The plugin creates two types of endpoints for each relationship:
|
|
53
|
-
|
|
54
|
-
### 1. Relationship Endpoints (Linkage)
|
|
55
|
-
- **URL Pattern**: `/api/{resource}/{id}/relationships/{relationshipName}`
|
|
56
|
-
- **Purpose**: View and manage just the linkage data (IDs and types)
|
|
57
|
-
- **Example**: `/api/books/1/relationships/authors`
|
|
58
|
-
- **Returns**: Minimal data showing which resources are connected
|
|
59
|
-
|
|
60
|
-
### 2. Related Resource Endpoints
|
|
61
|
-
- **URL Pattern**: `/api/{resource}/{id}/{relationshipName}`
|
|
62
|
-
- **Purpose**: Fetch the full related resources
|
|
63
|
-
- **Example**: `/api/books/1/authors`
|
|
64
|
-
- **Returns**: Complete resource data for all related items
|
|
65
|
-
|
|
66
|
-
## Working with Relationships
|
|
67
|
-
|
|
68
|
-
Let's explore each operation using our book catalog system.
|
|
69
|
-
|
|
70
|
-
### Setup Test Data
|
|
71
|
-
|
|
72
|
-
First, let's create some test data to work with:
|
|
73
|
-
|
|
74
|
-
```javascript
|
|
75
|
-
// Create a country
|
|
76
|
-
const usa = await api.resources.countries.post({
|
|
77
|
-
name: 'United States',
|
|
78
|
-
code: 'US'
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Create authors
|
|
82
|
-
const stephenKing = await api.resources.authors.post({
|
|
83
|
-
name: 'Stephen King'
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const peterStraub = await api.resources.authors.post({
|
|
87
|
-
name: 'Peter Straub'
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Create a publisher
|
|
91
|
-
const scribner = await api.resources.publishers.post({
|
|
92
|
-
name: 'Scribner',
|
|
93
|
-
country_id: usa.id
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Create a book with initial relationships
|
|
97
|
-
const talisman = await api.resources.books.post({
|
|
98
|
-
title: 'The Talisman',
|
|
99
|
-
country_id: usa.id,
|
|
100
|
-
publisher_id: scribner.id
|
|
101
|
-
}, {
|
|
102
|
-
simplified: false // Use full JSON:API format
|
|
103
|
-
});
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Viewing Relationship Links
|
|
107
|
-
|
|
108
|
-
Get just the relationship linkage data without fetching full resources:
|
|
109
|
-
|
|
110
|
-
**Programmatic:**
|
|
111
|
-
```javascript
|
|
112
|
-
const bookAuthorsRelationship = await api.resources.books.getRelationship({
|
|
113
|
-
id: talisman.data.id,
|
|
114
|
-
relationshipName: 'authors'
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
console.log(bookAuthorsRelationship);
|
|
118
|
-
// Output:
|
|
119
|
-
// {
|
|
120
|
-
// data: [], // Empty because we haven't added authors yet
|
|
121
|
-
// links: {
|
|
122
|
-
// self: "/api/books/1/relationships/authors",
|
|
123
|
-
// related: "/api/books/1/authors"
|
|
124
|
-
// }
|
|
125
|
-
// }
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**HTTP:**
|
|
129
|
-
```bash
|
|
130
|
-
curl -X GET http://localhost:3000/api/books/1/relationships/authors
|
|
131
|
-
|
|
132
|
-
# Response:
|
|
133
|
-
# {
|
|
134
|
-
# "data": [],
|
|
135
|
-
# "links": {
|
|
136
|
-
# "self": "/api/books/1/relationships/authors",
|
|
137
|
-
# "related": "/api/books/1/authors"
|
|
138
|
-
# }
|
|
139
|
-
# }
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Fetching Related Resources
|
|
143
|
-
|
|
144
|
-
Get the full related resources with all their attributes:
|
|
145
|
-
|
|
146
|
-
**Programmatic:**
|
|
147
|
-
```javascript
|
|
148
|
-
const bookAuthors = await api.resources.books.getRelated({
|
|
149
|
-
id: talisman.data.id,
|
|
150
|
-
relationshipName: 'authors',
|
|
151
|
-
queryParams: {
|
|
152
|
-
fields: { authors: 'name' } // Optional: sparse fieldsets
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
console.log(bookAuthors);
|
|
157
|
-
// Output:
|
|
158
|
-
// {
|
|
159
|
-
// data: [
|
|
160
|
-
// {
|
|
161
|
-
// type: "authors",
|
|
162
|
-
// id: "1",
|
|
163
|
-
// attributes: { name: "Stephen King" }
|
|
164
|
-
// }
|
|
165
|
-
// ]
|
|
166
|
-
// }
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
**HTTP:**
|
|
170
|
-
```bash
|
|
171
|
-
curl -X GET 'http://localhost:3000/api/books/1/authors?fields[authors]=name'
|
|
172
|
-
|
|
173
|
-
# Response:
|
|
174
|
-
# {
|
|
175
|
-
# "data": [
|
|
176
|
-
# {
|
|
177
|
-
# "type": "authors",
|
|
178
|
-
# "id": "1",
|
|
179
|
-
# "attributes": { "name": "Stephen King" }
|
|
180
|
-
# }
|
|
181
|
-
# ]
|
|
182
|
-
# }
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Adding Relationships
|
|
186
|
-
|
|
187
|
-
Add new relationships without replacing existing ones (only for to-many relationships):
|
|
188
|
-
|
|
189
|
-
**Programmatic:**
|
|
190
|
-
```javascript
|
|
191
|
-
// Add Stephen King and Peter Straub as authors
|
|
192
|
-
const addAuthorsResult = await api.resources.books.postRelationship({
|
|
193
|
-
id: talisman.data.id,
|
|
194
|
-
relationshipName: 'authors',
|
|
195
|
-
inputRecord: {
|
|
196
|
-
data: [
|
|
197
|
-
{ type: 'authors', id: stephenKing.id },
|
|
198
|
-
{ type: 'authors', id: peterStraub.id }
|
|
199
|
-
]
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
console.log('Authors added successfully');
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
**HTTP:**
|
|
207
|
-
```bash
|
|
208
|
-
curl -X POST http://localhost:3000/api/books/1/relationships/authors \
|
|
209
|
-
-H "Content-Type: application/vnd.api+json" \
|
|
210
|
-
-d '{
|
|
211
|
-
"data": [
|
|
212
|
-
{ "type": "authors", "id": "1" },
|
|
213
|
-
{ "type": "authors", "id": "2" }
|
|
214
|
-
]
|
|
215
|
-
}'
|
|
216
|
-
|
|
217
|
-
# Response: 204 No Content (success)
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Replacing Relationships
|
|
221
|
-
|
|
222
|
-
Replace all existing relationships with a new set:
|
|
223
|
-
|
|
224
|
-
**Programmatic:**
|
|
225
|
-
```javascript
|
|
226
|
-
// Replace all authors with just Stephen King
|
|
227
|
-
const replaceAuthorsResult = await api.resources.books.patchRelationship({
|
|
228
|
-
id: talisman.data.id,
|
|
229
|
-
relationshipName: 'authors',
|
|
230
|
-
inputRecord: {
|
|
231
|
-
data: [
|
|
232
|
-
{ type: 'authors', id: stephenKing.id }
|
|
233
|
-
]
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// For to-one relationships, you can also set to null
|
|
238
|
-
const removePublisher = await api.resources.books.patchRelationship({
|
|
239
|
-
id: talisman.data.id,
|
|
240
|
-
relationshipName: 'publisher',
|
|
241
|
-
inputRecord: {
|
|
242
|
-
data: null
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
**HTTP:**
|
|
248
|
-
```bash
|
|
249
|
-
# Replace all authors
|
|
250
|
-
curl -X PATCH http://localhost:3000/api/books/1/relationships/authors \
|
|
251
|
-
-H "Content-Type: application/vnd.api+json" \
|
|
252
|
-
-d '{
|
|
253
|
-
"data": [
|
|
254
|
-
{ "type": "authors", "id": "1" }
|
|
255
|
-
]
|
|
256
|
-
}'
|
|
257
|
-
|
|
258
|
-
# Remove publisher (set to null)
|
|
259
|
-
curl -X PATCH http://localhost:3000/api/books/1/relationships/publisher \
|
|
260
|
-
-H "Content-Type: application/vnd.api+json" \
|
|
261
|
-
-d '{
|
|
262
|
-
"data": null
|
|
263
|
-
}'
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Removing Relationships
|
|
267
|
-
|
|
268
|
-
Remove specific relationships without affecting others (only for to-many relationships):
|
|
269
|
-
|
|
270
|
-
**Programmatic:**
|
|
271
|
-
```javascript
|
|
272
|
-
// Remove Peter Straub from the book's authors
|
|
273
|
-
const removeAuthorResult = await api.resources.books.deleteRelationship({
|
|
274
|
-
id: talisman.data.id,
|
|
275
|
-
relationshipName: 'authors',
|
|
276
|
-
inputRecord: {
|
|
277
|
-
data: [
|
|
278
|
-
{ type: 'authors', id: peterStraub.id }
|
|
279
|
-
]
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
**HTTP:**
|
|
285
|
-
```bash
|
|
286
|
-
curl -X DELETE http://localhost:3000/api/books/1/relationships/authors \
|
|
287
|
-
-H "Content-Type: application/vnd.api+json" \
|
|
288
|
-
-d '{
|
|
289
|
-
"data": [
|
|
290
|
-
{ "type": "authors", "id": "2" }
|
|
291
|
-
]
|
|
292
|
-
}'
|
|
293
|
-
|
|
294
|
-
# Response: 204 No Content (success)
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
## Relationship Types
|
|
298
|
-
|
|
299
|
-
The plugin handles all relationship types defined in your schema:
|
|
300
|
-
|
|
301
|
-
### belongsTo Relationships
|
|
302
|
-
|
|
303
|
-
Books belong to publishers:
|
|
304
|
-
|
|
305
|
-
```javascript
|
|
306
|
-
// View the publisher relationship
|
|
307
|
-
const bookPublisher = await api.resources.books.getRelationship({
|
|
308
|
-
id: talisman.data.id,
|
|
309
|
-
relationshipName: 'publisher'
|
|
310
|
-
});
|
|
311
|
-
// Returns: { data: { type: "publishers", id: "1" }, links: {...} }
|
|
312
|
-
|
|
313
|
-
// Change the publisher
|
|
314
|
-
await api.resources.books.patchRelationship({
|
|
315
|
-
id: talisman.data.id,
|
|
316
|
-
relationshipName: 'publisher',
|
|
317
|
-
inputRecord: {
|
|
318
|
-
data: { type: 'publishers', id: '2' }
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### hasOne Relationships
|
|
324
|
-
|
|
325
|
-
The inverse of belongsTo (automatically created):
|
|
326
|
-
|
|
327
|
-
```javascript
|
|
328
|
-
// If you define country → publishers (hasMany)
|
|
329
|
-
// Each publisher has one country (implicit hasOne)
|
|
330
|
-
const publisherCountry = await api.resources.publishers.getRelationship({
|
|
331
|
-
id: scribner.id,
|
|
332
|
-
relationshipName: 'country'
|
|
333
|
-
});
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### hasMany Relationships
|
|
337
|
-
|
|
338
|
-
Publishers have many books:
|
|
339
|
-
|
|
340
|
-
```javascript
|
|
341
|
-
// View all books for a publisher
|
|
342
|
-
const publisherBooks = await api.resources.publishers.getRelated({
|
|
343
|
-
id: scribner.id,
|
|
344
|
-
relationshipName: 'books',
|
|
345
|
-
queryParams: {
|
|
346
|
-
sort: '-year', // Sort by year descending
|
|
347
|
-
filter: { inStock: true } // Only in-stock books
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### Many-to-Many Relationships
|
|
353
|
-
|
|
354
|
-
Books have many authors through the book_authors pivot table:
|
|
355
|
-
|
|
356
|
-
```javascript
|
|
357
|
-
// This is the most flexible relationship type
|
|
358
|
-
// Supports POST (add), PATCH (replace), and DELETE (remove)
|
|
359
|
-
const bookAuthors = await api.resources.books.getRelationship({
|
|
360
|
-
id: talisman.data.id,
|
|
361
|
-
relationshipName: 'authors'
|
|
362
|
-
});
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
## Security and Permissions
|
|
366
|
-
|
|
367
|
-
The plugin respects your existing security setup and adds specific hooks:
|
|
368
|
-
|
|
369
|
-
```javascript
|
|
370
|
-
// Add permission checks for relationship operations
|
|
371
|
-
api.addHook('checkPermissionsGetRelationship', async ({ context }) => {
|
|
372
|
-
// Check if user can view this relationship
|
|
373
|
-
if (!context.auth?.userId) {
|
|
374
|
-
throw new Error('Authentication required');
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
api.addHook('checkPermissionsPostRelationship', async ({ context }) => {
|
|
379
|
-
// Check if user can add relationships
|
|
380
|
-
const { scopeName, relationshipName } = context;
|
|
381
|
-
|
|
382
|
-
if (scopeName === 'books' && relationshipName === 'authors') {
|
|
383
|
-
// Only editors can modify book authors
|
|
384
|
-
if (context.auth?.role !== 'editor') {
|
|
385
|
-
throw new Error('Only editors can modify book authors');
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
## Error Handling
|
|
392
|
-
|
|
393
|
-
Common errors you might encounter:
|
|
394
|
-
|
|
395
|
-
### Relationship Not Found
|
|
396
|
-
```javascript
|
|
397
|
-
try {
|
|
398
|
-
await api.resources.books.getRelationship({
|
|
399
|
-
id: '1',
|
|
400
|
-
relationshipName: 'invalid'
|
|
401
|
-
});
|
|
402
|
-
} catch (error) {
|
|
403
|
-
// RestApiResourceError: Relationship 'invalid' not found on resource 'books'
|
|
404
|
-
}
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### Invalid Operation
|
|
408
|
-
```javascript
|
|
409
|
-
try {
|
|
410
|
-
// Can't POST to a to-one relationship
|
|
411
|
-
await api.resources.books.postRelationship({
|
|
412
|
-
id: '1',
|
|
413
|
-
relationshipName: 'publisher', // belongsTo is to-one
|
|
414
|
-
inputRecord: { data: { type: 'publishers', id: '1' } }
|
|
415
|
-
});
|
|
416
|
-
} catch (error) {
|
|
417
|
-
// RestApiValidationError: POST operation not allowed on to-one relationship
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### Resource Not Found
|
|
422
|
-
```javascript
|
|
423
|
-
try {
|
|
424
|
-
await api.resources.books.getRelationship({
|
|
425
|
-
id: '999', // Non-existent book
|
|
426
|
-
relationshipName: 'authors'
|
|
427
|
-
});
|
|
428
|
-
} catch (error) {
|
|
429
|
-
// RestApiResourceError: Resource not found
|
|
430
|
-
}
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
## Best Practices
|
|
434
|
-
|
|
435
|
-
### 1. Use Relationship Endpoints for Bulk Operations
|
|
436
|
-
|
|
437
|
-
Instead of updating each book individually to add an author:
|
|
438
|
-
```javascript
|
|
439
|
-
// ❌ Inefficient
|
|
440
|
-
for (const bookId of bookIds) {
|
|
441
|
-
const book = await api.resources.books.get({ id: bookId });
|
|
442
|
-
await api.resources.books.patch({
|
|
443
|
-
id: bookId,
|
|
444
|
-
inputRecord: {
|
|
445
|
-
data: {
|
|
446
|
-
type: 'books',
|
|
447
|
-
id: bookId,
|
|
448
|
-
relationships: {
|
|
449
|
-
authors: {
|
|
450
|
-
data: [...book.data.relationships.authors.data, newAuthor]
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// ✅ Efficient
|
|
459
|
-
for (const bookId of bookIds) {
|
|
460
|
-
await api.resources.books.postRelationship({
|
|
461
|
-
id: bookId,
|
|
462
|
-
relationshipName: 'authors',
|
|
463
|
-
inputRecord: {
|
|
464
|
-
data: [newAuthor]
|
|
465
|
-
}
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
### 2. Use Links for API Discovery
|
|
471
|
-
|
|
472
|
-
The `links` object in responses helps clients navigate your API:
|
|
473
|
-
```javascript
|
|
474
|
-
const relationship = await api.resources.books.getRelationship({
|
|
475
|
-
id: '1',
|
|
476
|
-
relationshipName: 'authors'
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
console.log(relationship.links);
|
|
480
|
-
// {
|
|
481
|
-
// self: "/api/books/1/relationships/authors",
|
|
482
|
-
// related: "/api/books/1/authors"
|
|
483
|
-
// }
|
|
484
|
-
|
|
485
|
-
// Client can use these links directly
|
|
486
|
-
const fullAuthors = await fetch(relationship.links.related);
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
### 3. Choose the Right Endpoint
|
|
490
|
-
|
|
491
|
-
- **Use relationship endpoints** when you only need to manage connections
|
|
492
|
-
- **Use related endpoints** when you need full resource data
|
|
493
|
-
- **Use regular PATCH** when updating multiple aspects of a resource
|
|
494
|
-
|
|
495
|
-
### 4. Handle Transactions Properly
|
|
496
|
-
|
|
497
|
-
The plugin automatically handles transactions for data integrity:
|
|
498
|
-
```javascript
|
|
499
|
-
// This is atomic - either all authors are added or none
|
|
500
|
-
await api.resources.books.postRelationship({
|
|
501
|
-
id: bookId,
|
|
502
|
-
relationshipName: 'authors',
|
|
503
|
-
inputRecord: {
|
|
504
|
-
data: [
|
|
505
|
-
{ type: 'authors', id: '1' },
|
|
506
|
-
{ type: 'authors', id: '2' },
|
|
507
|
-
{ type: 'authors', id: '3' }
|
|
508
|
-
]
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
## Summary
|
|
514
|
-
|
|
515
|
-
The REST API Relationships Plugin transforms relationships from second-class citizens to fully manageable resources. It provides efficient, standards-compliant endpoints that make working with related data intuitive and performant. By following JSON:API specifications, it ensures your API remains consistent and predictable for clients.
|
|
516
|
-
|
|
517
|
-
Whether you're building a simple blog or a complex e-commerce system, relationship endpoints help you create cleaner, more maintainable APIs that scale with your application's needs.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Data and relations
|
|
2
|
-
|
|
3
|
-
The true power of json-rest-api lies in its sophisticated handling of relationships between resources. Unlike traditional ORMs that focus on object mapping, this library provides a fully JSON:API compliant REST interface that elegantly manages complex data relationships. This guide will walk you through defining datasets and explore all the possible relationship types available in the system.
|
|
4
|
-
|
|
5
|
-
This guide has been split into the following sections for easier navigation:
|
|
6
|
-
|
|
7
|
-
## [2.1 The starting point](./GUIDE_2_1_The_Starting_Point.md)
|
|
8
|
-
- Basic setup and configuration
|
|
9
|
-
- Creating the API instance
|
|
10
|
-
- Setting up the database connection
|
|
11
|
-
|
|
12
|
-
## [2.2 Manipulating and searching tables with no relationships](./GUIDE_2_2_Manipulating_And_Searching_Tables.md)
|
|
13
|
-
- Setting up schemas
|
|
14
|
-
- Basic CRUD operations (POST, GET, PATCH, PUT)
|
|
15
|
-
- Search and filtering capabilities
|
|
16
|
-
- Multi-word search with AND logic
|
|
17
|
-
- Custom search functions
|
|
18
|
-
- Sparse fieldsets
|
|
19
|
-
|
|
20
|
-
## [2.3 `belongsTo` Relationships](./GUIDE_2_3_BelongsTo_Relationships.md)
|
|
21
|
-
- Understanding belongsTo relationships
|
|
22
|
-
- Including related records
|
|
23
|
-
- Sparse fieldsets with belongsTo relations
|
|
24
|
-
- Filtering by belongsTo relationships
|
|
25
|
-
|
|
26
|
-
## [2.4 hasMany records](./GUIDE_2_4_HasMany_Records.md)
|
|
27
|
-
- Understanding hasMany relationships
|
|
28
|
-
- Including hasMany records
|
|
29
|
-
- Filtering by hasMany relationships
|
|
30
|
-
- Cross-table searches
|
|
31
|
-
|
|
32
|
-
## [2.5 hasMany records (polymorphic)](./GUIDE_2_5_HasMany_Polymorphic.md)
|
|
33
|
-
- Understanding polymorphic relationships
|
|
34
|
-
- Including polymorphic records
|
|
35
|
-
- Forward and reverse polymorphic search
|
|
36
|
-
- Complex polymorphic filtering
|
|
37
|
-
|
|
38
|
-
## [2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md)
|
|
39
|
-
- Understanding many-to-many relationships
|
|
40
|
-
- Working with pivot tables directly
|
|
41
|
-
- Including many-to-many records
|
|
42
|
-
- Search across many-to-many relationships
|
|
43
|
-
|
|
44
|
-
## [2.7 Pagination and ordering](./GUIDE_2_7_Pagination_And_Ordering.md)
|
|
45
|
-
- Offset-based vs cursor-based pagination
|
|
46
|
-
- Sorting and multi-field sorting
|
|
47
|
-
- Limits and ordering for included relationships
|
|
48
|
-
- Combining filters, sorting, pagination, and includes
|
|
49
|
-
|
|
50
|
-
## [2.8 Effects of PUT and PATCH](./GUIDE_2_8_Effects_of_PUT_and_PATCH.md)
|
|
51
|
-
- Understanding the difference between PUT and PATCH
|
|
52
|
-
- How PUT affects relationships and existing stored fields
|
|
53
|
-
- How PATCH affects relationships (partial updates)
|
|
54
|
-
- Managing belongsTo relationships
|
|
55
|
-
- Handling hasMany relationships
|
|
56
|
-
- Working with many-to-many relationships
|
|
57
|
-
|
|
58
|
-
## [2.9 Relationships URLs](./GUIDE_2_9_Relationships_Urls.md)
|
|
59
|
-
- Working with relationship endpoints
|
|
60
|
-
- Direct relationship manipulation
|
|
61
|
-
- Relationship links and meta information
|
|
62
|
-
- Managing relationship collections
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
[Back to Guide](./index.md)
|