Why is Laravel Lighthouse GraphQL running multiple MySQL queries (not eager loading)? - laravel

I am new to GraphQL and Lighthouse.
My understanding from https://lighthouse-php.com/4.0/performance/n-plus-one.html#eager-loading-relationships is that for a nested GraphQL query, "Under the hood, Lighthouse will batch the relationship queries together in a single database query."
However, I see in the logs all of these queries where there should be only 1:
select count(*) as aggregate from `posts`
select * from `posts` order by `created_at` asc limit 20 offset 0
select * from `post_files` where `post_files`.`post_id` in (249, 5...
select * from `post_tags` where `post_tags`.`post_id` in (249, 5...
select * from `comments` where `comments`.`post_id` in (249, 5...
select * from `files` where `files`.`id` in (269, 615, ...
select * from `tags` where `tags`.`id` in (2, 3, 4, ...
Here is my GraphQL query:
gql`
query Posts($page: Int) {
posts(
first: 20
page: $page,
orderBy: { field: CREATED_AT, order: ASC }
) {
paginatorInfo {
currentPage
hasMorePages
total
}
data {
id
content
created_at
postFiles {
file {
id
original_name
extension
}
}
postTags {
id
tag {
id
label
}
}
comments {
id
user_id
message
created_at
seen_at
}
}
}
}
`;
My schema.graphql has the right #belongsTo and #hasMany directives:
"A date string with format `Y-m-d`, e.g. `2011-05-23`."
scalar Date #scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date")
"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`."
scalar DateTime
#scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
"A datetime and timezone string in ISO 8601 format `Y-m-dTH:i:sO`, e.g. `2020-04-20T13:53:12+02:00`."
scalar DateTimeTz
#scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTimeTz")
type Comment {
id: Int!
user: User! #belongsTo
user_id: Int!
post: Post! #belongsTo
post_id: Int!
message: String!
emailed_at: DateTime
seen_at: DateTime
created_at: DateTime!
modified_at: DateTime!
}
type File {
id: Int!
original_name: String!
extension: String!
postFiles: [PostFile!]! #hasMany
}
type Post {
id: Int!
user: User! #belongsTo
user_id: Int!
content: String
origin: String
created_at: DateTime!
modified_at: DateTime!
postFiles: [PostFile!]! #hasMany
comments: [Comment!]! #hasMany
postTags: [PostTag!]! #hasMany
}
type PostFile {
post: Post! #belongsTo
post_id: Int!
file: File! #belongsTo
file_id: Int!
}
type PostTag {
id: Int!
post: Post! #belongsTo
post_id: Int!
tag: Tag! #belongsTo
tag_id: Int!
created_at: DateTime!
modified_at: DateTime!
}
type Tag {
id: Int!
label: String!
hash: String!
created_at: DateTime!
modified_at: DateTime!
postTags: [PostTag!]! #hasMany
}
type User {
id: Int!
email: String!
password: String!
salt: String
db_key: String
created_at: DateTime!
modified_at: DateTime!
posts: [Post!]! #hasMany
comments: [Comment!]! #hasMany
}
type Query {
comments: [Comment!]! #all
files: [File!]! #all
posts(
orderBy: _ #orderBy(columns: ["id", "created_at"], order: ASC)
): [Post!]! #paginate(defaultCount: 20)
post(id: ID! #eq): Post #find
postFiles: [PostFile!]! #all
postTags: [PostTag!]! #all
tags: [Tag!]! #all
users: [User!]! #all
}
My Laravel models have all the right relationships. E.g. class Post has:
public function comments() {
return $this->hasMany(Comment::class, Comment::POST_ID);
}
And class Comment has:
public function post() {
return $this->belongsTo(Post::class);
}
and so on.

You must use Type hinting
import these types
use Illuminate\Database\Eloquent\Relations\HasMany;
And
use Illuminate\Database\Eloquent\Relations\BelongsTo;
public function post() : BelongsTo {
return $this->belongsTo(Post::class);
}

Related

GraphQL extract possible queries from a schema

I have a graphql schema that i'm parsing
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
author: User!
comments: [Comment!]!
}
type Comment {
id: ID!
body: String!
author: User!
post: Post!
}
type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
post(id: ID!): Post
comments: [Comment!]!
comment(id: ID!): Comment
}
i want to get extract each possible query from it as a seperate string
in the above example,
String 1
type Query {
users: [User!]!
}
String 2
type Query {
user(id: ID!): User
}
String 3
type Query {
posts: [Post!]!
}
String 4
type Query {
post(id: ID!): Post
}
String 5
type Query {
comments: [Comment!]!
}
String 6
type Query {
comment(id: ID!): Comment
}
what is the best way to achieve this ? i'm using graphql javascript package

GraphQL - Model(s) belong to specific user field

I'm using Lighthouse package to implement GraphQL, the users in my app belong to an entity and I need to get models that belong to the entity of each user, my schema is like this for the moment
"A date string with format `Y-m-d`, e.g. `2011-05-23`."
scalar Date #scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date")
"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`."
scalar DateTime #scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
type Query {
me: User #auth
execution: [RobotTime!]! #all
}
type RobotTime {
id: ID!
instance: Instance!
robot: Robot!
start: DateTime!
end: DateTime!
}
type Instance {
id: ID!
name: String!
token: String!
}
type Robot {
id: ID!
start_bot: String!
process: Process!
}
type Process {
id: ID!
name: String!
token: String!
}
type User {
id: ID!
name: String!
email: String!
created_at: String!
updated_at: String
}
I have searched the documentation, but I can't find anything that helps me to do what I need to do.
Currently I have it done in a controller and I return it as a json with a not so complex logic, there are about 5 models that I use.
Sorry for my bad English
You need to declare the appropriate relation in your User model in laravel, sth like this:
public function entity(): BelongsTo
{
return $this->belongsTo(Entity::class);
}
Then add the relation in User type in graphql:
type User {
id: ID!
name: String!
email: String!
created_at: DateTime!
updated_at: DateTime
entity: Entity #belongsTo
}
Of course, you'll need to declare an Entity model in laravel and type in graphql.

Declare variable in lighthouse graphql files

I've created a graphql file like this:
type Field {
id: ID!
name_en: String!
name_fa: String!
description: String!
img_url: String!
created_at: DateTime!
updated_at: DateTime!
subFields: [SubField] #hasMany
}
extend type Query {
fields(input: ListInput #spread): [Field] #paginate(model: "App\\Models\\CustomerManagement\\BusinessInformation\\Field" defaultCount: 10)
field(id: ID! #eq): Field #find(model: "App\\Models\\CustomerManagement\\BusinessInformation\\Field")
}
extend type Mutation {
createField(
name_en: String! #rules(apply: ["required"])
name_fa: String
description: String
img_url: String
): Field #create(model: "App\\Models\\CustomerManagement\\BusinessInformation\\Field")
updateField(
id: ID! #rules(apply: ["required", "int"])
name_en: String #rules(apply: ["required"])
name_fa: String
description: String
img_url: String
): Field #update(model: "App\\Models\\CustomerManagement\\BusinessInformation\\Field")
deleteField(
id: ID! #rules(apply: ["required", "int"])
): Field #delete(model: "App\\Models\\CustomerManagement\\BusinessInformation\\Field")
}
Every time I want to refer to Field model, I have to type the whole App\\Models\\CustomerManagement\\BusinessInformation\\Field. I was wondering if I could declare a variable like model_namespace and use it instead of the big model namespace.
EDIT:
https://lighthouse-php.com/4.4/api-reference/directives.html#namespace
You should use the #namespace directive
extend type Query #namespace(field: "App\\Blog") {
posts: [Post!]! #field(resolver: "Post#resolveAll")
}
You have a couple of options:
You can basically load a default namespace array using namespace configuration:
https://github.com/nuwave/lighthouse/pull/525/files
Or alternatively use a group directive:
https://lighthouse-php.com/3.0/api-reference/directives.html#group

Field on `User` type returns `null` for Query even though data exists

I have a profilePicture field on my User type that is being returned as null even though I can see the data is there in the database. I have the following setup:
// datamodel.prisma
enum ContentType {
IMAGE
VIDEO
}
type Content #embedded {
type: ContentType! #default(value: IMAGE)
url: String
publicId: String
}
type User {
id: ID! #id
name: String
username: String! #unique
profilePicture: Content
website: String
bio: String
email: String! #unique
phoneNumber: Int
gender: Gender! #default(value: NOTSPECIFIED)
following: [User!]! #relation(name: "Following", link: INLINE)
followers: [User!]! #relation(name: "Followers", link: INLINE)
likes: [Like!]! #relation(name: "UserLikes")
comments: [Comment!]! #relation(name: "UserComments")
password: String!
resetToken: String
resetTokenExpiry: String
posts: [Post!]! #relation(name: "Posts")
verified: Boolean! #default(value: false)
permissions: [Permission!]! #default(value: USER)
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
}
// schema.graphql
type User {
id: ID!
name: String!
username: String!
profilePicture: Content
website: String
bio: String
email: String!
phoneNumber: Int
gender: Gender!
following: [User!]!
followers: [User!]!
verified: Boolean
posts: [Post!]!
likes: [Like!]!
comments: [Comment!]!
permissions: [Permission!]!
}
Like I said there is data in the database but when I run the below query in Playground I get null:
// query
{
user(id: "5c8e5fb424aa9a000767c6c0") {
profilePicture {
url
}
}
}
// response
{
"data": {
"user": {
"profilePicture": null
}
}
}
Any idea why?
ctx.prisma.user(({ id }), info); doesn’t return profilePicture even though the field exists in generated/prisma.graphql
Fixed it. I had to add a field resolver for profilePicture under User. I've done this before for related fields like posts and comments and I think It's because profilePicture points to the #embedded Content type so is sort of a related field as well.
{
User: {
posts: parent => prisma.user({ id: parent.id }).posts(),
following: parent => prisma.user({ id: parent.id }).following(),
followers: parent => prisma.user({ id: parent.id }).followers(),
likes: parent => prisma.user({ id: parent.id }).likes(),
comments: parent => prisma.user({ id: parent.id }).comments(),
profilePicture: parent => prisma.user({ id: parent.id }).profilePicture()
}
}

authenticate user and serve only their related data

I have a schema in graphcool with these nodes (not sure what the correct term is here... leaf? node? model? type??)
type User #model {
auth0UserId: String #isUnique
createdAt: DateTime!
id: ID! #isUnique
userIdentifier: String
bundleIdentifier: String
updatedAt: DateTime!
devices: [Device!]! #relation(name: "UserOnDevice")
tokens: [Token!]! #relation(name: "TokenOnUser")
}
type Device #model {
id: ID! #isUnique
deviceIdentifier: String!
users: [User!]! #relation(name: "UserOnDevice")
token: Token #relation(name: "DeviceOnToken")
}
I'd like to make it so that a user must be authenticated and be related to the device data to be able to query on it. So, for a query like:
query($deviceIdentifier: String!) {
device(deviceIdentifier: $deviceIdentifier) {
id
}
}
This should return null unless they are autthenticated and are a user in the specified relation. I was thinking I needed a permission query like this one:
query ($node_id: ID!, $user_id: ID!) {
SomeDeviceExists(filter: {
id: $node_id,
users: {
id: $user_id
}
})
}
But it turns out that is invalid. How do I do it?
query ($node_id: ID!, $user_id: ID!) {
SomeDeviceExists(filter: {
id: $node_id,
users_some: {
id: $user_id
}
})
}
but this does require submitting the user_id in the request.

Resources