Let's say I have two Arrays of Objects:
let movies = [
{ id: '1', title: 'Erin Brockovich'},
{ id: '2', title: 'A Good Year'},
{ id: '3', title: 'A Beautiful Mind'},
{ id: '4', title: 'Gladiator'}
];
let actors = [
{ id: 'a', name: 'Julia Roberts'},
{ id: 'b', name: 'Albert Finney'},
{ id: 'c', name: 'Russell Crowe'}
];
I want to create many-to-many relationship betweet them. For the begining in Vanilla JavaScript , eventually in GraphQL schema.
In JavaScript I did something like this:
let movies = [
{ id: '1', title: 'Erin Brockovich', actorId: ['a', 'b'] },
{ id: '2', title: 'A Good Year', actorId: ['b', 'c'] },
{ id: '3', title: 'A Beautiful Mind', actorId: ['c'] },
{ id: '4', title: 'Gladiator', actorId: ['c'] }
];
let actors = [
{ id: 'a', name: 'Julia Roberts', movieId: ['1'] },
{ id: 'b', name: 'Albert Finney', movieId: ['1', '2'] },
{ id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4'] }
];
let actorIds = [];
let movieIds = [];
for (let m = 0; m < movies.length; m ++) {
for (let i = 0; i < movies[m].actorId.length; i ++) {
actorIds.push(movies[m].actorId[i]);
}
}
for (let a = 0; a < actors.length; a ++) {
for (let i = 0; i < actors[a].movieId.length; i ++) {
movieIds.push(actors[a].movieId[i]);
}
}
for (let a = 0; a < actors.length; a ++) {
for (let i = 0; i < actorIds.length; i ++) {
if ((actors[a].id == 'c') && (actors[a].id == actorIds[i])) {
for (let j = 0; j < movies.length; j ++) {
if (movies[j].id == movieIds[i]) {
console.log(movies[j].title);
}
}
}
}
}
When I run preceding code in Node, Terminal will return
A Good Year
A Beautiful Mind
Gladiator
and this is exactly what I want.
Unfortunately I've get lost in GraphQL schema. What I have so far—inside of fields
function of course—is this:
in_which_movies: {
type: new GraphQLList(FilmType),
resolve(parent, args) {
let actorIds = [];
let movieIds = [];
for (let m = 0; m < movies.length; m ++) {
for (let i = 0; i < movies[m].actorId.length; i ++) {
actorIds.push(movies[m].actorId[i]);
}
}
for (let a = 0; a < actors.length; a ++) {
for (let i = 0; i < actors[a].movieId.length; i ++) {
movieIds.push(actors[a].movieId[i]);
}
}
for (var a = 0; a < actors.length; a ++) {
for (var i = 0; i < actorIds.length; i ++) {
if ((actors[a].id == parent.id) && (actors[a].id == actorIds[i])) {
for (var j = 0; j < movies.length; j ++) {
if (movies[j].id == movieIds[i]) {
console.log(movies[j].title);
}
}
}
}
}
return movies[j].title;
}
}
When I run the following query in GraphiQL...
{
actor(id: "c") {
name
in_which_movies {
title
}
}
}
...I have this response:
{
"errors": [
{
"message": "Cannot read property 'title' of undefined",
"locations": [
{
"line": 4,
"column": 3
}
],
"path": [
"actor",
"in_which_movies"
]
}
],
"data": {
"actor": {
"name": "Russell Crowe",
"in_which_movies": null
}
}
}
...which is strange for me, cuz Terminal is responding as I expected
A Good Year
A Beautiful Mind
Gladiator
I guess all code I've wrote so far is useless and I need some fresh guidelines how to write many-to-many relationship in GraphQL properly.
GraphQL works by sending operations to an endpoint. There are three types of operations: queries, mutations, and subscriptions. A query is sent through an HTTP POST call to retrieve data. A mutation is also sent through a HTTP POST and is used to modify data.
You can use the object (one-to-one) or array (one-to-many) relationships defined in your schema to make a nested query i.e. fetch data for a type along with data from a nested or related type. The name of the nested object is the same as the name of the object/array relationship configured in the console.
GraphQL scalar types Here are the primitive data types that GraphQL supports: Int – A signed 32-bit integer. Float – A signed double precision floating point value.
TL;DR I think you're way overthinking things. Your resolvers are doing way too much work and that's resulting in code that's hard to reason about and hard to debug.
I don't think your question really has much to do with GraphQL but just getting the right operations on your underlying data. I'll try to step through it starting with your example and in terms of GraphQL so we end up with the types and resolvers you're looking for.
Starting with your vanilla code:
let movies = [
{ id: '1', title: 'Erin Brockovich', actorId: ['a', 'b'] },
{ id: '2', title: 'A Good Year', actorId: ['b', 'c'] },
{ id: '3', title: 'A Beautiful Mind', actorId: ['c'] },
{ id: '4', title: 'Gladiator', actorId: ['c'] }
];
let actors = [
{ id: 'a', name: 'Julia Roberts', movieId: ['1'] },
{ id: 'b', name: 'Albert Finney', movieId: ['1', '2'] },
{ id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4'] }
];
I'd like to suggest that we translate this into something indexed by id
so that it's easier to query. That will also better model a database or key-value store that you'll usually have behind a production GraphQL API.
Translating this to something indexed, but still vanilla JS:
let movies = {
'1': { id: '1', title: 'Erin Brockovich', actorId: ['a', 'b'] },
'2': { id: '2', title: 'A Good Year', actorId: ['b', 'c'] },
'3': { id: '3', title: 'A Beautiful Mind', actorId: ['c'] },
'4': { id: '4', title: 'Gladiator', actorId: ['c'] }
};
let actors = {
'a': { id: 'a', name: 'Julia Roberts', movieId: ['1'] },
'b': { id: 'b', name: 'Albert Finney', movieId: ['1', '2'] },
'c': { id: 'c', name: 'Russell Crowe', movieId: ['2', '3', '4'] }
};
Next, we should think about the GraphQL schema that will represent these types. Here's where the "many to many" part comes into play. I think we can pretty cleanly derive the types from your example data:
type Movie {
id: ID!
title: String
actors: [Actor]
}
type Actor {
id: ID!
name: String
movies: [Movie]
}
Note that [Movie]
is a list of Movie
objects. Even though the underlying data contains ids (aka "normalized", which is what we would expect) we model the API in terms of the actual typed relationships.
Next we need to set up the resolvers. Let's look at the Actor
type's resolvers since that's what is in your example.
movies: {
type: new GraphQLList(FilmType),
resolve(parent) {
// The ids of all the movies this actor is in. "parent" will be the
// actor data currently being queried
let movieIds = parent.movieId;
// We'll build up a list of the actual movie datas to return.
let actorInMovies = [];
for (let m = 0; m < movieIds.length; m++) {
// The m'th movie id.
let movieId = movieIds[m];
// The movie data from our indexed "movies" top level object.
// In production, this might access a database service
let movie = movies[movieID];
// Add that movie to the list of movies
actorInMovies.push(movie)
}
// Then we'll return that list of movie objects.
return actorInMovies;
}
}
Notice that in your original resolver, you returned movies[j].title
which is probably a string, and doesn't match up with what would be expected by "List of FilmType", and in my example above an array of movie data objects is returned.
Also, the code above is a quite verbose way to do this, but I thought it would be helpful to comment on each step. To be truly many-to-many, then the Movie
type should have nearly identical code for it's actors
field. However just to show an example of how this code can be greatly simplified by using the .map()
operator, I'll write it another way:
actors: {
type: new GraphQLList(ActorType),
resolve(parent) {
// In this case, "parent" will be the movie data currently being queried.
// Use "map" to convert a list of actor ids into a list of actor data
// objects using the indexed "actors" top level object.
return parent.actorId.map(id => actors[id]);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With