A "Component" pattern for GraphQL
Problem
The GraphQL API does not support Union types, yet you need query results that are comparable to using Unions.
This recipe is one solution to the problem outlined in Composable Types without Unions.
Solution
The following "component" pattern describes entities as being a single
type built from various components, rather than as individual types. It
is similar to the "sparse fields" introduced in
the previous
recipe:
it uses a single Type for all entities, but rather than put additional
fields at the top level, you wrap the entities' data into various
@embedded
types. This pattern provides more control over types and
GraphQL queries.
Not all entities have the same properties: A movie does not have a number of pages like a book, and a book does not have a length of time like a movie. However, entities of different types can share some properties, such as whether they are "new releases" or are being "promoted" somehow. This pattern allows us to reuse the same component to describe multiple types, similar to how Union types would.
Setup
-
Update your GraphQL schema to include:
type User { name: String! @unique favorites: [MediaItem]! @relation } type MediaItem { title: String! favoritedBy: [User]! @relation components: Components } type Components @embedded { # components with data movie: Movie, show: Show, book: Book, # singleton components promoted: Boolean newRelease: Boolean } type Movie @embedded { length: Float! # ... other Movie fields } type Show @embedded { seasons: Int! # ... other Show fields } type Book @embedded { pages: Int! # ... other Book fields } type Query { itemsWithComponent(component: String!): [MediaItem]! @resolver(paginated: true) itemsWithAllComponents(components: [String!]!): [MediaItem]! @resolver(paginated: true) itemsWithAnyComponents(components: [String!]!): [MediaItem]! @resolver(paginated: true) }
This involves editing the GraphQL schema definition, wherever you have stored it, and then uploading the new schema in the Fauna Dashboard.
-
Create a new Index to support some custom resolvers.
The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.
ObjectV(ref: RefV(id = "MediaItem_has_component", collection = RefV(id = "indexes")),ts: LongV(1654791391840000),active: BooleanV(True),serialized: BooleanV(True),name: StringV(MediaItem_has_component),source: ObjectV(collection: RefV(id = "MediaItem", collection = RefV(id = "collections")),fields: ObjectV(components: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))),terms: Arr(ObjectV(binding: StringV(components))),partitions: LongV(1))
The Go version of this example is not currently available.
{ref: ref(id = "MediaItem_has_component", collection = ref(id = "indexes")), ts: 1654791419640000, active: true, serialized: true, name: "MediaItem_has_component", source: {collection: ref(id = "MediaItem", collection = ref(id = "collections")), fields: {components: QueryV({api_version=4, lambda=doc, expr={let={components={select=[data, components], from={var=doc}, default={object={}}}, component_keys={map={lambda=c, expr={select=0, from={var=c}}}, collection={to_array={var=components}}}}, in={var=component_keys}}})}}, terms: [{binding: "components"}], partitions: 1}
{ ref: Index("MediaItem_has_component"), ts: 1654791433510000, active: true, serialized: true, name: 'MediaItem_has_component', source: { collection: Collection("MediaItem"), fields: { components: Query(Lambda("doc", Let({"components": Select(["data", "components"], Var("doc"), {}), "component_keys": Map(ToArray(Var("components")), Lambda("c", Select(0, Var("c"))))}, Var("component_keys")))) } }, terms: [ { binding: 'components' } ], partitions: 1 }
{'ref': Ref(id=MediaItem_has_component, collection=Ref(id=indexes)), 'ts': 1654791436680000, 'active': True, 'serialized': True, 'name': 'MediaItem_has_component', 'source': {'collection': Ref(id=MediaItem, collection=Ref(id=collections)), 'fields': {'components': Query({'api_version': '4', 'lambda': 'doc', 'expr': {'let': {'components': {'select': ['data', 'components'], 'from': {'var': 'doc'}, 'default': {'object': {}}}, 'component_keys': {'map': {'lambda': 'c', 'expr': {'select': 0, 'from': {'var': 'c'}}}, 'collection': {'to_array': {'var': 'components'}}}}, 'in': {'var': 'component_keys'}}})}}, 'terms': [{'binding': 'components'}], 'partitions': 1}
{ ref: Index("MediaItem_has_component"), ts: 1654791439330000, active: true, serialized: true, name: 'MediaItem_has_component', source: { collection: Collection("MediaItem"), fields: { components: Query(Lambda("doc", Let({"components": Select(["data", "components"], Var("doc"), {}), "component_keys": Map(ToArray(Var("components")), Lambda("c", Select(0, Var("c"))))}, Var("component_keys")))) } }, terms: [ { binding: 'components' } ], partitions: 1 }
This index is required by the UDF resolvers defined in the following steps.
-
Create a UDF to aid pagination with custom resolvers.
Each of the resolver UDFs that we need to create involve pagination. Since the pagination logic is identical in each case, we can create a helper UDF that the resolver UDFs can call.
The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.
ObjectV(ref: RefV(id = "paginate_helper", collection = RefV(id = "functions")),ts: LongV(1654791395650000),name: StringV(paginate_helper),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
The Go version of this example is not currently available.
{ref: ref(id = "paginate_helper", collection = ref(id = "functions")), ts: 1654791431950000, name: "paginate_helper", body: QueryV({api_version=4, lambda=[match, size, after, before], expr={if={equals=[{var=before}, null]}, then={if={equals=[{var=after}, null]}, then={paginate={var=match}, size={var=size}}, else={paginate={var=match}, after={var=after}, size={var=size}}}, else={paginate={var=match}, before={var=before}, size={var=size}}}})}
{ ref: Function("paginate_helper"), ts: 1654791433630000, name: 'paginate_helper', body: Query(Lambda(["match", "size", "after", "before"], If(Equals(Var("before"), null), If(Equals(Var("after"), null), Paginate(Var("match"), {"size": Var("size")}), Paginate(Var("match"), {"after": Var("after"), "size": Var("size")})), Paginate(Var("match"), {"before": Var("before"), "size": Var("size")})))) }
{'ref': Ref(id=paginate_helper, collection=Ref(id=functions)), 'ts': 1655424431870000, 'name': 'paginate_helper', 'body': Query({'api_version': '4', 'lambda': ['match', 'size', 'after', 'before'], 'expr': {'if': {'equals': [{'var': 'before'}, None]}, 'then': {'if': {'equals': [{'var': 'after'}, None]}, 'then': {'paginate': {'var': 'match'}, 'size': {'var': 'size'}}, 'else': {'paginate': {'var': 'match'}, 'after': {'var': 'after'}, 'size': {'var': 'size'}}}, 'else': {'paginate': {'var': 'match'}, 'before': {'var': 'before'}, 'size': {'var': 'size'}}}})}
{ ref: Function("paginate_helper"), ts: 1654791439670000, name: 'paginate_helper', body: Query(Lambda(["match", "size", "after", "before"], If(Equals(Var("before"), null), If(Equals(Var("after"), null), Paginate(Var("match"), {"size": Var("size")}), Paginate(Var("match"), {"after": Var("after"), "size": Var("size")})), Paginate(Var("match"), {"before": Var("before"), "size": Var("size")})))) }
-
Update the auto-generated
itemsWithComponent
resolver UDF.Auto-generated resolver UDFs are only stub functions, so we have to provide the implementation that we need.
The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.
ObjectV(ref: RefV(id = "itemsWithComponent", collection = RefV(id = "functions")),ts: LongV(1654896505910000),name: StringV(itemsWithComponent),data: ObjectV(gql: ObjectV(ts: FaunaTime(2022-06-10T21:28:16.399Z),meta: ObjectV(location: StringV(Query),field: ObjectV(name: StringV(itemsWithComponent),directives: Arr(ObjectV(name: StringV(resolver),args: ObjectV(name: StringV(itemsWithComponent),paginated: BooleanV(True)))),type: ObjectV(NotNull: ObjectV(List: ObjectV(Named: StringV(MediaItem)))),arguments: Arr(ObjectV(name: StringV(component),type: ObjectV(NotNull: ObjectV(Named: StringV(String))))))))),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
The Go version of this example is not currently available.
{ref: ref(id = "itemsWithComponent", collection = ref(id = "functions")), ts: 1654897645750000, name: "itemsWithComponent", data: {gql: {ts: 2022-06-10T21:46:50.548Z, meta: {location: "Query", field: {name: "itemsWithComponent", directives: [{name: "resolver", args: {name: "itemsWithComponent", paginated: true}}], type: {NotNull: {List: {Named: "MediaItem"}}}, arguments: [{name: "component", type: {NotNull: {Named: "String"}}}]}}}}, body: QueryV({api_version=4, lambda=[component, size, after, before], expr={let={match={match={index=MediaItem_has_component}, terms={var=component}}, page={call={function=paginate_helper}, arguments=[{var=match}, {var=size}, {var=after}, {var=before}]}}, in={map={lambda=ref, expr={get={var=ref}}}, collection={var=page}}}})}
{ ref: Function("itemsWithComponent"), ts: 1654893973790000, name: 'itemsWithComponent', data: { gql: { ts: Time("2022-06-10T20:46:13.490Z"), meta: { location: 'Query', field: { name: 'itemsWithComponent', directives: [ { name: 'resolver', args: { name: 'itemsWithComponent', paginated: true } } ], type: { NotNull: { List: { Named: 'MediaItem' } } }, arguments: [ { name: 'component', type: { NotNull: { Named: 'String' } } } ] } } } }, body: Query(Lambda(["component", "size", "after", "before"], Let({"match": Match(Index("MediaItem_has_component"), Var("component")), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref"))))))) }
{'ref': Ref(id=itemsWithComponent, collection=Ref(id=functions)), 'ts': 1654898104170000, 'name': 'itemsWithComponent', 'data': {'gql': {'ts': FaunaTime('2022-06-10T21:55:01.598Z'), 'meta': {'location': 'Query', 'field': {'name': 'itemsWithComponent', 'directives': [{'name': 'resolver', 'args': {'name': 'itemsWithComponent', 'paginated': True}}], 'type': {'NotNull': {'List': {'Named': 'MediaItem'}}}, 'arguments': [{'name': 'component', 'type': {'NotNull': {'Named': 'String'}}}]}}}}, 'body': Query({'api_version': '4', 'lambda': ['component', 'size', 'after', 'before'], 'expr': {'let': {'match': {'match': {'index': 'MediaItem_has_component'}, 'terms': {'var': 'component'}}, 'page': {'call': 'paginate_helper', 'arguments': [{'var': 'match'}, {'var': 'size'}, {'var': 'after'}, {'var': 'before'}]}}, 'in': {'map': {'lambda': 'ref', 'expr': {'get': {'var': 'ref'}}}, 'collection': {'var': 'page'}}}})}
{ ref: Function("itemsWithComponent"), ts: 1654893376660000, name: 'itemsWithComponent', data: { gql: { ts: Time("2022-06-10T20:36:15.483Z"), meta: { location: 'Query', field: { name: 'itemsWithComponent', directives: [ { name: 'resolver', args: { name: 'itemsWithComponent', paginated: true } } ], type: { NotNull: { List: { Named: 'MediaItem' } } }, arguments: [ { name: 'component', type: { NotNull: { Named: 'String' } } } ] } } } }, body: Query(Lambda(["component", "size", "after", "before"], Let({"match": Match(Index("MediaItem_has_component"), Var("component")), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref"))))))) }
-
Update the auto-generated
itemsWithAllComponents
resolver UDF.The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.
ObjectV(ref: RefV(id = "itemsWithAllComponents", collection = RefV(id = "functions")),ts: LongV(1654900917990000),name: StringV(itemsWithAllComponents),data: ObjectV(gql: ObjectV(ts: FaunaTime(2022-06-10T22:41:44.598Z),meta: ObjectV(location: StringV(Query),field: ObjectV(name: StringV(itemsWithAllComponents),directives: Arr(ObjectV(name: StringV(resolver),args: ObjectV(name: StringV(itemsWithAllComponents),paginated: BooleanV(True)))),type: ObjectV(NotNull: ObjectV(List: ObjectV(Named: StringV(MediaItem)))),arguments: Arr(ObjectV(name: StringV(components),type: ObjectV(NotNull: ObjectV(List: ObjectV(NotNull: ObjectV(Named: StringV(String))))))))))),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
The Go version of this example is not currently available.
{ref: ref(id = "itemsWithAllComponents", collection = ref(id = "functions")), ts: 1654899752410000, name: "itemsWithAllComponents", data: {gql: {ts: 2022-06-10T22:21:45.316Z, meta: {location: "Query", field: {name: "itemsWithAllComponents", directives: [{name: "resolver", args: {name: "itemsWithAllComponents", paginated: true}}], type: {NotNull: {List: {Named: "MediaItem"}}}, arguments: [{name: "components", type: {NotNull: {List: {NotNull: {Named: "String"}}}}}]}}}}, body: QueryV({api_version=4, lambda=[components, size, after, before], expr={let={match={intersection={map={lambda=component, expr={match={index=MediaItem_has_component}, terms={var=component}}}, collection={var=components}}}, page={call={function=paginate_helper}, arguments=[{var=match}, {var=size}, {var=after}, {var=before}]}}, in={map={lambda=ref, expr={get={var=ref}}}, collection={var=page}}}})}
{ ref: Function("itemsWithAllComponents"), ts: 1654899476190000, name: 'itemsWithAllComponents', data: { gql: { ts: Time("2022-06-10T22:17:55.662Z"), meta: { location: 'Query', field: { name: 'itemsWithAllComponents', directives: [ { name: 'resolver', args: { name: 'itemsWithAllComponents', paginated: true } } ], type: { NotNull: { List: { Named: 'MediaItem' } } }, arguments: [ { name: 'components', type: { NotNull: { List: { NotNull: { Named: 'String' } } } } } ] } } } }, body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Intersection(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref"))))))) }
{'ref': Ref(id=itemsWithAllComponents, collection=Ref(id=functions)), 'ts': 1654899353360000, 'name': 'itemsWithAllComponents', 'data': {'gql': {'ts': FaunaTime('2022-06-10T22:15:49.739Z'), 'meta': {'location': 'Query', 'field': {'name': 'itemsWithAllComponents', 'directives': [{'name': 'resolver', 'args': {'name': 'itemsWithAllComponents', 'paginated': True}}], 'type': {'NotNull': {'List': {'Named': 'MediaItem'}}}, 'arguments': [{'name': 'components', 'type': {'NotNull': {'List': {'NotNull': {'Named': 'String'}}}}}]}}}}, 'body': Query({'api_version': '4', 'lambda': ['components', 'size', 'after', 'before'], 'expr': {'let': {'match': {'intersection': {'map': {'lambda': 'component', 'expr': {'match': {'index': 'MediaItem_has_component'}, 'terms': {'var': 'component'}}}, 'collection': {'var': 'components'}}}, 'page': {'call': 'paginate_helper', 'arguments': [{'var': 'match'}, {'var': 'size'}, {'var': 'after'}, {'var': 'before'}]}}, 'in': {'map': {'lambda': 'ref', 'expr': {'get': {'var': 'ref'}}}, 'collection': {'var': 'page'}}}})}
{ ref: Function("itemsWithAllComponents"), ts: 1654899106920000, name: 'itemsWithAllComponents', data: { gql: { ts: Time("2022-06-10T22:11:45.474Z"), meta: { location: 'Query', field: { name: 'itemsWithAllComponents', directives: [ { name: 'resolver', args: { name: 'itemsWithAllComponents', paginated: true } } ], type: { NotNull: { List: { Named: 'MediaItem' } } }, arguments: [ { name: 'components', type: { NotNull: { List: { NotNull: { Named: 'String' } } } } } ] } } } }, body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Intersection(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref"))))))) }
The Intersection
function is used to match all components. -
Update the
itemsWithAnyComponents
resolver UDF.The following code sample includes variations for the supported Drivers, as well as a Shell example that you can copy+paste into the Shell tab in the Fauna Dashboard.
ObjectV(ref: RefV(id = "itemsWithAnyComponents", collection = RefV(id = "functions")),ts: LongV(1655142746710000),name: StringV(itemsWithAnyComponents),data: ObjectV(gql: ObjectV(ts: FaunaTime(2022-06-13T17:52:08.929Z),meta: ObjectV(location: StringV(Query),field: ObjectV(name: StringV(itemsWithAnyComponents),directives: Arr(ObjectV(name: StringV(resolver),args: ObjectV(name: StringV(itemsWithAnyComponents),paginated: BooleanV(True)))),type: ObjectV(NotNull: ObjectV(List: ObjectV(Named: StringV(MediaItem)))),arguments: Arr(ObjectV(name: StringV(components),type: ObjectV(NotNull: ObjectV(List: ObjectV(NotNull: ObjectV(Named: StringV(String))))))))))),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
The Go version of this example is not currently available.
{ref: ref(id = "itemsWithAnyComponents", collection = ref(id = "functions")), ts: 1655142852770000, name: "itemsWithAnyComponents", data: {gql: {ts: 2022-06-13T17:53:12.289Z, meta: {location: "Query", field: {name: "itemsWithAnyComponents", directives: [{name: "resolver", args: {name: "itemsWithAnyComponents", paginated: true}}], type: {NotNull: {List: {Named: "MediaItem"}}}, arguments: [{name: "components", type: {NotNull: {List: {NotNull: {Named: "String"}}}}}]}}}}, body: QueryV({api_version=4, lambda=[components, size, after, before], expr={let={match={union={map={lambda=component, expr={match={index=MediaItem_has_component}, terms={var=component}}}, collection={var=components}}}, page={call={function=paginate_helper}, arguments=[{var=match}, {var=size}, {var=after}, {var=before}]}}, in={map={lambda=ref, expr={get={var=ref}}}, collection={var=page}}}})}
{ ref: Function("itemsWithAnyComponents"), ts: 1655142530560000, name: 'itemsWithAnyComponents', data: { gql: { ts: Time("2022-06-13T17:48:49.894Z"), meta: { location: 'Query', field: { name: 'itemsWithAnyComponents', directives: [ { name: 'resolver', args: { name: 'itemsWithAnyComponents', paginated: true } } ], type: { NotNull: { List: { Named: 'MediaItem' } } }, arguments: [ { name: 'components', type: { NotNull: { List: { NotNull: { Named: 'String' } } } } } ] } } } }, body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Union(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref"))))))) }
{'ref': Ref(id=itemsWithAnyComponents, collection=Ref(id=functions)), 'ts': 1655142956620000, 'name': 'itemsWithAnyComponents', 'data': {'gql': {'ts': FaunaTime('2022-06-13T17:55:51.408Z'), 'meta': {'location': 'Query', 'field': {'name': 'itemsWithAnyComponents', 'directives': [{'name': 'resolver', 'args': {'name': 'itemsWithAnyComponents', 'paginated': True}}], 'type': {'NotNull': {'List': {'Named': 'MediaItem'}}}, 'arguments': [{'name': 'components', 'type': {'NotNull': {'List': {'NotNull': {'Named': 'String'}}}}}]}}}}, 'body': Query({'api_version': '4', 'lambda': ['components', 'size', 'after', 'before'], 'expr': {'let': {'match': {'union': {'map': {'lambda': 'component', 'expr': {'match': {'index': 'MediaItem_has_component'}, 'terms': {'var': 'component'}}}, 'collection': {'var': 'components'}}}, 'page': {'call': 'paginate_helper', 'arguments': [{'var': 'match'}, {'var': 'size'}, {'var': 'after'}, {'var': 'before'}]}}, 'in': {'map': {'lambda': 'ref', 'expr': {'get': {'var': 'ref'}}}, 'collection': {'var': 'page'}}}})}
{ ref: Function("itemsWithAnyComponents"), ts: 1654901367040000, name: 'itemsWithAnyComponents', data: { gql: { ts: Time("2022-06-10T22:49:25.339Z"), meta: { location: 'Query', field: { name: 'itemsWithAnyComponents', directives: [ { name: 'resolver', args: { name: 'itemsWithAnyComponents', paginated: true } } ], type: { NotNull: { List: { Named: 'MediaItem' } } }, arguments: [ { name: 'components', type: { NotNull: { List: { NotNull: { Named: 'String' } } } } } ] } } } }, body: Query(Lambda(["components", "size", "after", "before"], Let({"match": Union(Map(Var("components"), Lambda("component", Match(Index("MediaItem_has_component"), Var("component"))))), "page": Call("paginate_helper", [Var("match"), Var("size"), Var("after"), Var("before")])}, Map(Var("page"), Lambda("ref", Get(Var("ref"))))))) }
The Union
function is used to match any components. -
Run a GraphQL mutation to create a
User
and someMediaItem
s. Use the GraphQL tab in the Fauna Dashboard to execute the following mutation:The result should be similar to:
{ "data": { "createUser": { "name": "Alice", "_id": "332207514480280098", "favorites": { "data": [ { "title": "Black Widow", "components": { "movie": { "length": 2.2 }, "show": null, "book": null, "newRelease": true, "promoted": null }, "_id": "332207514484474402" }, { "title": "Psycho", "components": { "movie": { "length": 1.8 }, "show": null, "book": null, "newRelease": null, "promoted": true }, "_id": "332207514493911586" }, { "title": "The Expanse", "components": { "movie": null, "show": { "seasons": 6 }, "book": null, "newRelease": true, "promoted": null }, "_id": "332207514497057314" }, { "title": "Fellowship of the Ring", "components": { "movie": null, "show": null, "book": { "pages": 544 }, "newRelease": null, "promoted": true }, "_id": "332207514500203042" } ] } } } }
The document IDs presented in the _id
fields are different for every new document created in Fauna. The Objective 3 query requires two of the document IDs present in this result.
Objective 1: Query for all entities of a given type
You can get all items of a type by indexing on the existence of the
component
field.
{
"data": {
"allMovies": {
"data": [
{
"title": "Black Widow",
"components": {
"movie": {
"length": 2.2
},
"newRelease": true,
"promoted": null
}
},
{
"title": "Psycho",
"components": {
"movie": {
"length": 1.8
},
"newRelease": null,
"promoted": true
}
}
]
},
"allShows": {
"data": [
{
"title": "The Expanse",
"components": {
"show": {
"seasons": 6
},
"newRelease": true,
"promoted": null
}
}
]
},
"allBooks": {
"data": [
{
"title": "Fellowship of the Ring",
"components": {
"book": {
"pages": 544
},
"newRelease": null,
"promoted": true
}
}
]
}
}
}
Objective 2: Query for all entities with certain properties
Every entity is searchable by every property. You can use the current schema to match all items that have multiple components.
Additional Indexes can be created to extend the above schema to match on
terms such as data.components.book.author
.
{
"data": {
"allMovies": {
"data": [
{
"title": "Black Widow",
"components": {
"movie": {
"length": 2.2
},
"newRelease": true,
"promoted": null
}
}
]
}
}
}
Objective 3: Query for all relationships of an entity, regardless of which type it points to
-
You can search for all user favorites by selecting the single
favorites
field. -
You can search for all users that have the media item marked as a favorite through the
favoritedBy
field.
To make the following query work:
-
replace
<$USERID>
with the document ID for the user created in the setup’s step 7 (just below thename
field in the result). -
replace
<$MEDIAITEMID>
with the document ID for the "Black Widow" movie created in the setup’s step 7.
{
"data": {
"findUserByID": {
"name": "Alice",
"favorites": {
"data": [
{
"title": "Black Widow",
"components": {
"movie": {
"length": 2.2
},
"show": null,
"book": null,
"newRelease": true,
"promoted": null
}
},
{
"title": "Psycho",
"components": {
"movie": {
"length": 1.8
},
"show": null,
"book": null,
"newRelease": null,
"promoted": true
}
},
{
"title": "The Expanse",
"components": {
"movie": null,
"show": {
"seasons": 6
},
"book": null,
"newRelease": true,
"promoted": null
}
},
{
"title": "Fellowship of the Ring",
"components": {
"movie": null,
"show": null,
"book": {
"pages": 544
},
"newRelease": null,
"promoted": true
}
}
]
}
},
"findMediaItemByID": {
"title": "Black Widow",
"components": {
"movie": {
"length": 2.2
},
"show": null,
"book": null,
"newRelease": true,
"promoted": null
},
"favoritedBy": {
"data": [
{
"name": "Alice"
}
]
}
}
}
}
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!