Relations
When using type-graphql-query you can specify relations that should be exposed for the DTO using the following decorators. Relations allow you to define how entities are connected and automatically generate GraphQL endpoints for querying and managing those relationships.
What You’ll Learn
Section intitulée « What You’ll Learn »This guide will help you understand:
- ✅ Available relation decorators and when to use them
- ✅ How to define single-value and collection relations
- ✅ How to enable filtering on relations
- ✅ How to configure pagination for relations
- ✅ How to manage relation mutations (add, set, remove)
- ✅ Best practices for relation design
@Relation- A relation that is a single value (one-to-one, many-to-one)@FilterableRelation- A@Relationthat enables filtering the parent by fields of the relationDTO.@UnPagedRelation- An array of relations (e.g, many-to-many, one-to-many) that returns all of the related records.@FilterableUnPagedRelation- An@UnPagedRelationthat enables filtering the parent by fields of the relationDTO.@OffsetConnection- A connection that represents a collection (e.g, many-to-many, one-to-many) that usesoffsetbased pagination.@FilterableOffsetConnection- An@OffsetConnectionthat enables filtering the parent by fields of the connectionDTO.@CursorConnection- A connection that represents a collection (e.g, many-to-many, one-to-many) that usescursorbased pagination.@FilterableCursorConnection- A@CursorConnectionthat enables filtering the parent by fields of the connectionDTO.
In the following examples we are going to use the following entities.
ProductOrderLineOrder
@Relation
Section intitulée « @Relation »A relation that is a single value (one-to-one, many-to-one)
Based on the entities defined above we can add a product relation to the OrderLine by creating the following OrderLineDTO with a @Relation decorator.
import { ObjectType, ID } from '@lib/type-graphql';import { Relation, IDField, EnhancedField } from '@lib/type-graphql-query';import { Product } from '../product/product.type';
@ObjectType()@Relation('product', () => Product, { nullable: true,})export class OrderLine { @IDField(() => ID) id!: string;
@EnhancedField(() => String) productId!: string;
@EnhancedField(() => Number) unitPrice!: number;}The @Relation decorator lets type-graphql-query know to expose the following endpoints:
orderLine.product- Aqueryto retrieve theOrderLine’sProductsetProductOnOrderLine- Amutationto set theProducton anOrderLine(ifupdate.enabledis set totrue).removeProductFromOrderLine- Amutationto remove aProduct/OrderLinerelation (ifremove.enabledis set totrue).- NOTE This does not typically remove either record just removes the relation.
To set up the resolver you can use buildSchema or buildTypeDefsAndResolvers.
import { buildTypeDefsAndResolvers } from '@lib/type-graphql-query';import { OrderLineDTO } from './order-line/order-line.type';
const { resolvers, typeDefs } = await buildTypeDefsAndResolvers({ resolvers: [ { DTOClass: OrderLineDTO, // ... other options }, ], // ... other options});type-graphql-query will then automatically create the following graphql definition:
type OrderLine { id: ID! productId: String! unitPrice: Float! product: Product}The following mutations will also be automatically exposed (if enabled):
type Mutation { setProductOnOrderLine(input: SetProductOnOrderLineInput!): OrderLine!}
input SetProductOnOrderLineInput { id: ID! relationId: ID!}@FilterableRelation
Section intitulée « @FilterableRelation »The @FilterableRelation extends the @Relation decorator exposing the ability to filter the DTO that defines the relation by relation properties.
The @FilterableRelation decorator will only work with relations defined by the ORM used (e.g. drizzle-orm). If your relations are federated you cannot use the @FilterableRelation decorator.
In this example we’ll use the same entities defined above to create a graphql endpoint that allows filtering OrderLines by Product properties.
import { ObjectType, ID } from '@lib/type-graphql';import { FilterableRelation, IDField, EnhancedField } from '@lib/type-graphql-query';import { Product } from '../product/product.type';
@ObjectType()@FilterableRelation('product', () => Product, { nullable: true,})export class OrderLine { @IDField(() => ID) id!: string;
@EnhancedField(() => String) productId!: string;
@EnhancedField(() => Number) unitPrice!: number;}Notice the use of @FilterableRelation instead of @Relation, by using the @FilterableRelation version type-graphql-query will allow filtering on the product relation.
The resolver definition remains the same.
Now you can filter OrderLines by Product properties:
query { queryManyOrderLine(filter: { product: { name: { like: "%chicken%" } } }) { id unitPrice product { name } }}@UnPagedRelation
Section intitulée « @UnPagedRelation »An array of relations (e.g, many-to-many, one-to-many) that returns all of the related records.
In this example we’ll add a lines relation to the Order DTO that returns all OrderLines for an Order.
import { ObjectType, ID } from '@lib/type-graphql';import { UnPagedRelation, IDField, EnhancedField } from '@lib/type-graphql-query';import { OrderLine } from '../order-line/order-line.type';
@ObjectType()@UnPagedRelation('lines', () => OrderLine, { nullable: true,})export class Order { @IDField(() => ID) id!: string;
@EnhancedField(() => String) number!: string;
@EnhancedField(() => Number) totalAmount!: number;}The @UnPagedRelation decorator will expose:
order.lines- Aqueryto retrieve allOrderLinesfor anOrderaddOrderLinesToOrder- Amutationto addOrderLinesto anOrder(ifupdate.enabledis set totrue)setOrderLinesOnOrder- Amutationto setOrderLineson anOrder(ifupdate.enabledis set totrue)removeOrderLinesFromOrder- Amutationto removeOrderLinesfrom anOrder(ifremove.enabledis set totrue)
The GraphQL schema will include:
type Order { id: ID! number: String! totalAmount: Float! lines: [OrderLine!]}@FilterableUnPagedRelation
Section intitulée « @FilterableUnPagedRelation »The @FilterableUnPagedRelation extends the @UnPagedRelation decorator exposing the ability to filter the DTO that defines the relation by relation properties.
import { ObjectType, ID } from '@lib/type-graphql';import { FilterableUnPagedRelation, IDField, EnhancedField } from '@lib/type-graphql-query';import { OrderLine } from '../order-line/order-line.type';
@ObjectType()@FilterableUnPagedRelation('lines', () => OrderLine, { nullable: true,})export class Order { @IDField(() => ID) id!: string;
@EnhancedField(() => String) number!: string;}Now you can filter Orders by OrderLine properties:
query { queryManyOrder(filter: { lines: { unitPrice: { gte: 100 } } }) { id number lines { id unitPrice } }}@OffsetConnection
Section intitulée « @OffsetConnection »A connection that represents a collection (e.g, many-to-many, one-to-many) that uses offset based pagination.
import { ObjectType, ID } from '@lib/type-graphql';import { OffsetConnection, IDField, EnhancedField } from '@lib/type-graphql-query';import { OrderLine } from '../order-line/order-line.type';
@ObjectType()@OffsetConnection('lines', () => OrderLine, { nullable: true, enableTotalCount: true,})export class Order { @IDField(() => ID) id!: string;
@EnhancedField(() => String) number!: string;}The @OffsetConnection decorator will expose:
order.lines- Aqueryto retrieve paginatedOrderLinesfor anOrderusing offset paginationaddOrderLinesToOrder- Amutationto addOrderLinesto anOrder(ifupdate.enabledis set totrue)setOrderLinesOnOrder- Amutationto setOrderLineson anOrder(ifupdate.enabledis set totrue)removeOrderLinesFromOrder- Amutationto removeOrderLinesfrom anOrder(ifremove.enabledis set totrue)
The GraphQL schema will include:
type Order { id: ID! number: String! lines( paging: OffsetPaging filter: OrderLineFilter sorting: [OrderLineSort!] ): OrderOrderLineConnection}
type OrderOrderLineConnection { pageInfo: OffsetPageInfo! nodes: [OrderLine!]! totalCount: Int}Total Count Example
Section intitulée « Total Count Example »To enable total count in the connection, set enableTotalCount: true:
@OffsetConnection('lines', () => OrderLine, { enableTotalCount: true,})@FilterableOffsetConnection
Section intitulée « @FilterableOffsetConnection »The @FilterableOffsetConnection extends the @OffsetConnection decorator exposing the ability to filter the DTO that defines the relation by relation properties.
import { ObjectType, ID } from '@lib/type-graphql';import { FilterableOffsetConnection, IDField, EnhancedField } from '@lib/type-graphql-query';import { OrderLine } from '../order-line/order-line.type';
@ObjectType()@FilterableOffsetConnection('lines', () => OrderLine, { enableTotalCount: true,})export class Order { @IDField(() => ID) id!: string;}@CursorConnection
Section intitulée « @CursorConnection »A connection that represents a collection (e.g, many-to-many, one-to-many) that uses cursor based pagination.
import { ObjectType, ID } from '@lib/type-graphql';import { CursorConnection, IDField, EnhancedField } from '@lib/type-graphql-query';import { OrderLine } from '../order-line/order-line.type';
@ObjectType()@CursorConnection('lines', () => OrderLine, { nullable: true, enableTotalCount: true,})export class Order { @IDField(() => ID) id!: string;
@EnhancedField(() => String) number!: string;}The @CursorConnection decorator will expose:
order.lines- Aqueryto retrieve paginatedOrderLinesfor anOrderusing cursor paginationaddOrderLinesToOrder- Amutationto addOrderLinesto anOrder(ifupdate.enabledis set totrue)setOrderLinesOnOrder- Amutationto setOrderLineson anOrder(ifupdate.enabledis set totrue)removeOrderLinesFromOrder- Amutationto removeOrderLinesfrom anOrder(ifremove.enabledis set totrue)
The GraphQL schema will include:
type Order { id: ID! number: String! lines( paging: CursorPaging filter: OrderLineFilter sorting: [OrderLineSort!] ): OrderOrderLineConnection}
type OrderOrderLineConnection { pageInfo: PageInfo! edges: [OrderOrderLineEdge!]! totalCount: Int}
type OrderOrderLineEdge { node: OrderLine! cursor: ConnectionCursor!}Total Count Example
Section intitulée « Total Count Example »To enable total count in the connection, set enableTotalCount: true:
@CursorConnection('lines', () => OrderLine, { enableTotalCount: true,})@FilterableCursorConnection
Section intitulée « @FilterableCursorConnection »The @FilterableCursorConnection extends the @CursorConnection decorator exposing the ability to filter the DTO that defines the relation by relation properties.
import { ObjectType, ID } from '@lib/type-graphql';import { FilterableCursorConnection, IDField, EnhancedField } from '@lib/type-graphql-query';import { OrderLine } from '../order-line/order-line.type';
@ObjectType()@FilterableCursorConnection('lines', () => OrderLine, { enableTotalCount: true,})export class Order { @IDField(() => ID) id!: string;}All relation decorators accept the following options:
Common Options
Section intitulée « Common Options »| Option | Type | Default | Description |
|---|---|---|---|
nullable |
boolean |
false |
Set to true if the relation is nullable |
disableRead |
boolean |
false |
Disable read relation GraphQL endpoints |
enableLookAhead |
boolean |
false |
Enable look ahead mode, will join and select the relation when queried (only works with ORM-defined relations) |
description |
string |
- | Description of the relation |
relationName |
string |
Auto-generated | The name of the relation to use when fetching from the QueryService |
update |
ResolverRelationMethodOpts |
{ enabled: false } |
Options for update mutations |
remove |
ResolverRelationMethodOpts |
{ enabled: false } |
Options for remove mutations |
enableAggregate |
boolean |
false |
Enable aggregation queries |
aggregate |
ResolverRelationMethodOpts |
- | Options for aggregate queries |
enableTotalCount |
boolean |
false |
Include totalCount in connection (only for connections) |
middlewares |
Middleware[] |
[] |
Array of GraphQL middlewares to apply |
decorators |
(PropertyDecorator | MethodDecorator)[] |
[] |
Array of additional decorators to apply |
complexity |
Complexity |
- | Complexity function for the field |
Connection-Specific Options
Section intitulée « Connection-Specific Options »For @OffsetConnection, @FilterableOffsetConnection, @CursorConnection, and @FilterableCursorConnection, you can also pass options from QueryArgsTypeOpts to configure the query behavior:
defaultResultSize- Default number of resultsmaxResultsSize- Maximum number of resultsdefaultSort- Default sort orderenableSort- Enable sortingenableFilter- Enable filteringdefaultFilter- Default filterallowedBooleanExpressions- Allowed boolean operatorsfilterDepth- Maximum depth for nested filters
Custom Relation Name
Section intitulée « Custom Relation Name »Sometimes you may want to expose a relation that has a different name when persisted from the GraphQL property. To do this use the relationName property.
// Expose todoItem as productRelation in GraphQL@Relation('productRelation', () => Product, { relationName: 'product'})
// Expose subTasks as orderLinesConnection in GraphQL@CursorConnection('orderLinesConnection', () => OrderLine, { relationName: 'lines'})
// Expose subTasks as orderLinesConnection in GraphQL@OffsetConnection('orderLinesConnection', () => OrderLine, { relationName: 'lines'})
// Expose subTasks as orderLines in GraphQL@UnPagedRelation('orderLines', () => OrderLine, { relationName: 'lines'})GraphQL Lookahead
Section intitulée « GraphQL Lookahead »When you want to optimize your database queries you can set enableLookAhead, this will join and select the relation when it’s being fetched in the query.
// Enable enableLookAhead for the product relation@Relation('product', () => Product, { enableLookAhead: true})Disable Reads
Section intitulée « Disable Reads »To disable the read queries you can set the disableRead option to true.
// Disable reading the product relation@Relation('product', () => Product, { disableRead: true})
// Disable reading the connection@CursorConnection('lines', () => OrderLine, { disableRead: true})
// Disable reading the relation@OffsetConnection('lines', () => OrderLine, { disableRead: true})
// Disable reading the relation@UnPagedRelation('lines', () => OrderLine, { disableRead: true})Disable filter or sorting in relations
Section intitulée « Disable filter or sorting in relations »To disable the filter or sorting of relations you can set the disableFilter or/and disableSort option to true.
// Disable filtering and sorting for the connection@CursorConnection('lines', () => OrderLine, { disableFilter: true, disableSort: true})
// Disable filtering and sorting for the relation@OffsetConnection('lines', () => OrderLine, { disableFilter: true, disableSort: true})
// Disable filtering and sorting for the relation@UnPagedRelation('lines', () => OrderLine, { disableFilter: true, disableSort: true})Enable Updates
Section intitulée « Enable Updates »To enable the update mutations you can set the update.enabled option to true.
// Enable updates to the relation@Relation('product', () => Product, { update: { enabled: true }})
// Enable updating lines@CursorConnection('lines', () => OrderLine, { update: { enabled: true }})
// Enable updating lines@OffsetConnection('lines', () => OrderLine, { update: { enabled: true }})
// Enable updating lines@UnPagedRelation('lines', () => OrderLine, { update: { enabled: true }})Enable Removes
Section intitulée « Enable Removes »To enable the remove mutations you can set the remove.enabled option to true.
// Enable removing the relation@Relation('product', () => Product, { remove: { enabled: true }})
// Enable removing lines from the connection@CursorConnection('lines', () => OrderLine, { remove: { enabled: true }})
// Enable removing lines from the connection@OffsetConnection('lines', () => OrderLine, { remove: { enabled: true }})
// Enable removing lines from the relations@UnPagedRelation('lines', () => OrderLine, { remove: { enabled: true }})Middlewares and Decorators
Section intitulée « Middlewares and Decorators »In this example we’ll demonstrate using a middleware but the same pattern applies for decorators.
To set up a middleware for an endpoint you can use the middlewares option.
Assume we have the following auth middleware:
import { MiddlewareFn } from '@lib/type-graphql';import { MyContext } from './my-context';
export const AuthMiddleware: MiddlewareFn<MyContext> = async ({ context }, next) => { if (!context.userId) { throw new Error('Not authenticated'); } return next();};We can then add it to our relations:
// Add the AuthMiddleware using the middlewares option@Relation('product', () => Product, { middlewares: [AuthMiddleware]})
// Add the AuthMiddleware using the middlewares option@CursorConnection('lines', () => OrderLine, { middlewares: [AuthMiddleware]})
// Add the AuthMiddleware using the middlewares option@OffsetConnection('lines', () => OrderLine, { middlewares: [AuthMiddleware]})
// Add the AuthMiddleware using the middlewares option@UnPagedRelation('lines', () => OrderLine, { middlewares: [AuthMiddleware]})Now any requests that go to the update or remove endpoints will require the middleware.
Relation Mixin
Section intitulée « Relation Mixin »If you are using the resolvers individually you can use the following mixin to add relations functionality.
Relatable
Section intitulée « Relatable »When using The Relatable mixin adds all relations functionality to a resolver.
In this example we expose read endpoints for orders with the relations defined on the OrderDTO.
import { ReadResolver, Relatable } from '@lib/type-graphql-query';import { Resolver } from '@lib/type-graphql';import { OrderDTO } from './order/order.type';import { OrderService } from './order/order.service';
@Resolver(() => OrderDTO)export class OrderResolver extends Relatable(OrderDTO, { enableTotalCount: true,})(ReadResolver(OrderDTO)) { constructor(readonly service: OrderService) { super(service); }}The Relatable mixin will automatically add:
- Read relation endpoints (field resolvers)
- Update relation endpoints (if
update.enabledis set) - Remove relation endpoints (if
remove.enabledis set) - Aggregate relation endpoints (if
enableAggregateis set) - Reference relation endpoints (if
@Referencedecorators are used)
Complete Example
Section intitulée « Complete Example »Here’s a complete example showing multiple relation types:
import { ObjectType, ID } from '@lib/type-graphql';import { Relation, UnPagedRelation, CursorConnection, IDField, EnhancedField,} from '@lib/type-graphql-query';import { Product } from './product.type';import { OrderLine } from './order-line.type';import { Category } from './category.type';
@ObjectType()@Relation('organization', () => Organization, { nullable: true,})@UnPagedRelation('images', () => ProductImage, { nullable: true,})@UnPagedRelation('categories', () => Category, { nullable: true,})export class Product { @IDField(() => ID) id!: string;
@EnhancedField(() => String) name!: string;
@EnhancedField(() => String, { nullable: true }) organizationId!: string;}Best Practices
Section intitulée « Best Practices »-
Use the Right Relation Type
- Use
@Relationfor single-value relations (one-to-one, many-to-one) - Use
@UnPagedRelationfor small collections that don’t need pagination - Use
@OffsetConnectionor@CursorConnectionfor large collections
- Use
-
Enable Filtering When Needed
- Use
@FilterableRelation,@FilterableUnPagedRelation, etc. when you need to filter by relation properties - Only works with ORM-defined relations
- Use
-
Optimize with Lookahead
- Enable
enableLookAheadfor frequently accessed relations to reduce N+1 queries - Only works with ORM-defined relations
- Enable
-
Control Mutations
- Only enable
updateandremovemutations when needed - Use
middlewaresanddecoratorsto secure mutation endpoints
- Only enable
-
Use Total Count Sparingly
- Enable
enableTotalCountonly when needed as it can impact performance - Consider caching total count results for large datasets
- Enable
Quick Reference
Section intitulée « Quick Reference »| Decorator | Use Case | Pagination | Filtering |
|---|---|---|---|
@Relation |
Single value (one-to-one, many-to-one) | N/A | No |
@FilterableRelation |
Single value with filtering | N/A | Yes |
@UnPagedRelation |
Small collections | No | No |
@FilterableUnPagedRelation |
Small collections with filtering | No | Yes |
@OffsetConnection |
Large collections | Offset | No |
@FilterableOffsetConnection |
Large collections with filtering | Offset | Yes |
@CursorConnection |
Large collections | Cursor | No |
@FilterableCursorConnection |
Large collections with filtering | Cursor | Yes |
Managing Relations in Mutations
Section intitulée « Managing Relations in Mutations »When using upsertOne mutations, you can manage relations automatically using the RelationService. This allows you to create, update, and delete related records in a single operation.
Relation Types
Section intitulée « Relation Types »The system supports two types of relations:
-
“One” relations (many-to-one, one-to-one): Stored via a foreign key in the main table
- Example:
Organization.logo→ updateslogoFileIdin theorganizationtable
- Example:
-
“Many” relations (one-to-many, many-to-many): Stored in a separate relation table
- Example:
Organization.memberships→ manages theorganizationMembershiptable
- Example:
Configuration Options
Section intitulée « Configuration Options »You can configure relation management using decorator options:
For “One” Relations (@Relation, @FilterableRelation)
Section intitulée « For “One” Relations (@Relation, @FilterableRelation) »@Relation('logo', () => File, { nullable: true, // RelationService options foreignKeyFieldName: 'logoFileId', // Optional: auto-detected if not provided primaryKeyFieldName: 'id' // Optional: defaults to 'id'})For “Many” Relations (@UnPagedRelation, @OffsetConnection, etc.)
Section intitulée « For “Many” Relations (@UnPagedRelation, @OffsetConnection, etc.) »@UnPagedRelation('memberships', () => OrganizationMembership, { nullable: true, // RelationService options preserveExisting: false, // If true, preserves existing records not in input conflictColumns: ['userId', 'organizationId'], // Optional: auto-detected if not provided foreignKeyFieldName: 'organizationId', // Optional: auto-detected if not provided primaryKeyFieldName: 'id' // Optional: defaults to 'id'})Automatic Detection
Section intitulée « Automatic Detection »If options are not provided, the system automatically detects:
relationType: Determined by checking if relation fields exist in the main tableforeignKeyFieldName: Extracted from Drizzle relation metadataconflictColumns: For “many” relations, identifies foreign key columns in the relation table
Behavior Based on Input Values
Section intitulée « Behavior Based on Input Values »undefined
Section intitulée « undefined »No action is taken. The relation is not modified.
null or [] (empty array)
Section intitulée « null or [] (empty array) »- If
preserveExisting: false: Deletes all relations - If
preserveExisting: true: Does nothing (preserves existing relations)
Existing values
Section intitulée « Existing values »- Create: Items that don’t exist yet are created
- Update: Existing items are updated if their values have changed
- Delete: Items that are no longer in the input are deleted (unless
preserveExisting: true)
Examples
Section intitulée « Examples »Example 1: “One” Relation - Organization.logo
Section intitulée « Example 1: “One” Relation - Organization.logo »@ObjectType()@Relation('logo', () => File, { nullable: true, foreignKeyFieldName: 'logoFileId',})export class Organization { @IDField(() => ID) id!: string;
@EnhancedField(() => String) name!: string;}When upserting:
// Set logoawait upsertOneOrganization({ input: { id: 'org-123', name: 'My Organization', logo: { id: 'file-456' }, },});
// Remove logoawait upsertOneOrganization({ input: { id: 'org-123', name: 'My Organization', logo: null, },});Behavior:
- If
logo: { id: 'file-456' }→ UpdateslogoFileId = 'file-456'inorganization - If
logo: null→ UpdateslogoFileId = nullinorganization - If
logo: undefined→ Does not modifylogoFileId
Example 2: “Many” Relation - Organization.memberships
Section intitulée « Example 2: “Many” Relation - Organization.memberships »@ObjectType()@UnPagedRelation('memberships', () => OrganizationMembership, { nullable: true, preserveExisting: false, conflictColumns: ['userId', 'organizationId'], foreignKeyFieldName: 'organizationId',})export class Organization { @IDField(() => ID) id!: string;
@EnhancedField(() => String) name!: string;}When upserting:
await upsertOneOrganization({ input: { id: 'org-123', name: 'My Organization', memberships: [ { userId: 'user-1', type: 'OWNER' }, { userId: 'user-2', type: 'MEMBER' }, ], },});Behavior:
- Creates memberships that don’t exist yet
- Updates existing memberships if their values have changed
- Deletes memberships that are no longer in the input
Example 3: “Many” Relation with preserveExisting - Product.translations
Section intitulée « Example 3: “Many” Relation with preserveExisting - Product.translations »@ObjectType()@UnPagedRelation('translations', () => ProductTranslation, { nullable: true, preserveExisting: true, // Don't delete other locales conflictColumns: ['locale', 'productId'], foreignKeyFieldName: 'productId',})export class Product { @IDField(() => ID) id!: string;
@EnhancedField(() => String) name!: string;}When upserting:
await upsertOneProduct({ input: { id: 'product-123', name: 'My Product', translations: [{ locale: 'fr', name: 'Mon Produit' }], },});Behavior:
- Creates or updates the translation for
fr - Does not delete translations for
en,es, etc. (becausepreserveExisting: true)
Metadata Management
Section intitulée « Metadata Management »The service automatically manages metadata according to TableDescription configuration:
- Creation:
createdAt,createdBy(if configured) - Update:
updatedAt,updatedBy(if configured) - Deletion:
deletedAt,deletedBy(if soft delete configured)
Important Notes
Section intitulée « Important Notes »-
Operation Order: Relations are managed after the main table upsert, within the same transaction.
-
Transactions: All operations are performed in the same transaction, ensuring data consistency.
-
Soft Delete: If the relation table has soft delete configured, deletions use
deletedAt/deletedByinstead of hard delete. -
Performance: For “many” relations with many items, consider optimizing with bulk operations if necessary.
-
Validation: The service does not validate relation data. Ensure data is valid before calling.
-
“One” Relations Pre-processing: For “one” relations, the related record is upserted before the main record to obtain the foreign key ID.
Next Steps
Section intitulée « Next Steps »Continue learning:
- 🔧 Resolvers - Learn about resolver implementation
- 🔄 DataLoaders - Optimize data loading
- 🔍 Queries - Understand query endpoints
- ✏️ Mutations - Learn about mutation endpoints
Related Topics
Section intitulée « Related Topics »- Resolvers - Resolver implementation
- DataLoaders - Optimize data loading
- Queries - Query endpoints
- Mutations - Mutation endpoints
- QueryOptions - Configure query behavior