Relationships
Use extensions.relation on fields whose value is another GraphQL object or a list of objects. This metadata tells Simfinity how to store the relationship, generate inputs, and resolve related records.
Serie documentseasons: [{ number }]The parent stores the nested data.
Serie ← Season.serieSeparate documentsA child reference connects records with their own lifecycle.
Choose a storage model
| Relationship | Storage | Mutation input |
|---|---|---|
| Embedded object | Nested inside the parent document | The object's fields |
| Embedded list | Array inside the parent document | An array of nested inputs |
| Referenced object | ObjectId in the source document | { id: "..." } |
| Referenced collection | Child documents with a parent reference | { added, updated, deleted } |
An embedded object belongs to its parent document. A referenced type can have its own collection, endpoints, and lifecycle.
Embedded objects
A director profile can be stored directly in a serie. Register the supporting type without endpoints:
import {
GraphQLID,
GraphQLObjectType,
GraphQLString,
} from 'graphql';
import * as simfinity from '@simtlix/simfinity-js';
const DirectorType = new GraphQLObjectType({
name: 'Director',
fields: {
name: { type: GraphQLString },
country: { type: GraphQLString },
},
});
const SerieType = new GraphQLObjectType({
name: 'Serie',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
director: {
type: DirectorType,
extensions: { relation: { embedded: true } },
},
},
});
simfinity.addNoEndpointType(DirectorType);
simfinity.connect(null, SerieType, 'serie', 'series');
const schema = simfinity.createSchema();Create the serie and its director together:
mutation {
addserie(input: {
name: "Northern Lights"
director: { name: "Alex Rivera", country: "Argentina" }
}) {
id
name
director { name country }
}
}For an embedded list, use new GraphQLList(DirectorType) with the same embedded: true metadata and supply an array of objects. Updating an embedded object merges its supplied fields with the stored object. Supplying an embedded array replaces that array.
Referenced objects and collections
In this complete schema, each Season stores a reference to its Serie. The serie's seasons field reads back the matching child records:
import {
GraphQLID,
GraphQLInt,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLString,
} from 'graphql';
import * as simfinity from '@simtlix/simfinity-js';
const SerieType = new GraphQLObjectType({
name: 'Serie',
fields: () => ({
id: { type: GraphQLID },
name: { type: new GraphQLNonNull(GraphQLString) },
seasons: {
type: new GraphQLList(SeasonType),
extensions: {
relation: {
embedded: false,
connectionField: 'serie',
},
},
},
}),
});
const SeasonType = new GraphQLObjectType({
name: 'Season',
fields: () => ({
id: { type: GraphQLID },
number: { type: new GraphQLNonNull(GraphQLInt) },
year: { type: GraphQLInt },
serie: {
type: new GraphQLNonNull(SerieType),
extensions: {
relation: {
embedded: false,
connectionField: 'serie',
displayField: 'name',
},
},
},
}),
});
simfinity.connect(null, SerieType, 'serie', 'series');
simfinity.connect(null, SeasonType, 'season', 'seasons');
const schema = simfinity.createSchema();The fields: () => ({ ... }) functions defer access to the types, allowing both sides of the relationship to reference each other.
connectionField has two related roles: on Season.serie, it is the ObjectId storage field in a season; on Serie.seasons, it identifies the child's back-reference. Use the matching field name on both sides, as in this example, so nested creation and collection queries share the same link.
Specify the connection field
Always set connectionField explicitly for non-embedded relations. It is used during both materialization and resolution. displayField is a descriptive UI hint, not a uniqueness rule or a persistence field.
Create children with their parent
The added input for a referenced collection omits its parent connection field. Simfinity fills in the newly created parent's ID:
mutation {
addserie(input: {
name: "Northern Lights"
seasons: {
added: [
{ number: 1, year: 2024 }
{ number: 2, year: 2025 }
]
}
}) {
id
seasons { id number year }
}
}To create a season for an existing serie, pass an ID reference instead:
mutation AddSeason($serieId: String!) {
addseason(input: {
number: 3
year: 2026
serie: { id: $serieId }
}) {
id
serie { id name }
}
}The generated reference wrapper is IdInputType, whose id field is String!. Root entity IDs and update IDs use the GraphQL ID scalar; use the variable type required by the position you are filling.
Query related records
query {
series {
id
name
seasons(
year: { operator: GTE, value: 2025 }
sort: { terms: [{ field: "number", order: ASC }] }
pagination: { page: 1, size: 10 }
) {
id
number
year
}
}
}Referenced collections receive scalar filters, relationship filters, logical groups, sorting, and pagination. Filtering inside seasons(...) changes the returned children; it does not exclude the parent serie. To filter the parent by its children, put a relationship filter on the root series query, as shown in queries.
Update a collection
mutation EditSeasons($serieId: ID!, $seasonId: ID!, $removedId: ID!) {
updateserie(input: {
id: $serieId
seasons: {
added: [{ number: 4, year: 2027 }]
updated: [{ id: $seasonId, year: 2026 }]
deleted: [$removedId]
}
}) {
id
seasons { id number year }
}
}added creates records, updated changes records by ID, and deleted deletes child documents. These changes share the parent mutation's transaction.
Validate access to child IDs
The generated collection mutation handlers operate on the IDs you supply. They do not establish an ownership boundary for your application. Validate child ownership before updates and deletes when callers can submit arbitrary IDs. See authorization and controllers.
Deleting a parent does not automatically cascade through referenced collections. Implement the required deletion policy in your application. Existing field resolvers are preserved; Simfinity only generates a relation resolver when the field has none.