简体   繁体   中英

How to create a GraphQL query that returns data from multiple tables/models within one field using Laravel Lighthouse

Im trying to learn GraphQL with Laravel & Lighthouse and have a question Im hoping someone can help me with. I have the following five database tables which are also defined in my Laravel models:

  • users
  • books
  • user_books
  • book_series
  • book_copies

I'd like to create a GraphQL endpoint that allows me to get back an array of users and the books they own, where I can pull data from multiple tables into one subfield called "books" like so:

query {
    users {
        name
        books {
            title
            issue_number
            condition
            user_notes
        }
    }
}

To accomplish this in SQL is easy using joins like this:

$users = User::all();
foreach ($users as $user) {
    $user['books'] = DB::select('SELECT 
        book_series.title,
        book.issue_number
        book_copies.condition, 
        user_books.notes as user_notes
    FROM user_books 
    JOIN book_copies ON user_books.book_copy_id = book_copies.id 
    JOIN books ON book_copies.book_id = books.id 
    JOIN book_series ON books.series_id = book_series.id 
    WHERE user_books.user_id = ?',[$user['id']])->get();
}

How would I model this in my GraphQL schema file when the object type for "books" is a mashup of properties from four other object types (Book, UserBook, BookCopy, and BookSeries)?

Edit: I was able to get all the data I need by doing a query that looks like this:

users {
    name
    userBooks {
      user_notes
      bookCopy {
        condition
        book {
          issue_number
          series {
            title
          }
        }
      }
    }
}

However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.

I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions. Except for my other fields are matching the database column names which are lower_underscore. This is a slight nitpick.

Ok, after you edited your question, I will write the answer here, to answer your new questions.

However, as you can see, the data is separated into multiple child objects and is not as ideal as getting it all in one flat "books" object. If anyone knows how I might accomplish getting all the data back in one flat object, Id love to know.

The thing is, that this kind of fetching data is a central idea of GraphQL. You have some types, and these types may have some relations to each other. So you are able to fetch any relations of object, in any depth, even circular. Lighthouse gives you out of the box support to eloquent relations with batch loading, avoiding the N+1 performance problem. You also have to keep in mind - every field (literally, EVERY field) in your GraphQL definition is resolved on server. So there is a resolve function for each of the fields. So you are free to write your own resolver for particular fields. You actually can define a type in your GraphQL, that fits your initial expectation. Then you can define a root Query field eg fetchUsers , and create you custom field resolver. You can read in the docs, how it works and how to implement this: https://lighthouse-php.com/5.2/the-basics/fields.html#hello-world In this field resolver you are able to make your own data fetching, even without using any Laravel/Eloquent API. One thing you have to take care of - return a correct data type with the same structure as your return type in GraphQL for this field.

So to sum up - you have the option to do this. But in my opinion, you have to write more own code, cover it with tests on you own, which turns out in more work for you. I think it is simpler to use build-in directives, like @find , @paginate , @all in combination with relations-directives, which all covered with tests, and don't care about implementation.

I also noticed that the field names for the relationships need to match up exactly with my controller method names within each model, which are camelCase as per Laravel naming conventions.

You probably means methods within Model class, not controller. Lighthouse provides a @rename directive, which you can use to define different name in GraphQL for your attributes. For the relation directives you can pass an relation parameter, which will be used to fetch the data. so for your example you can use something like this:

type User {
   #...
   user_books: [Book!]! @hasMany(relation: "userBooks")
}

But in our project we decided to use snak_case also for relations, to keep GraphQL clean with consistent naming convention and less effort

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM