Laravel Lighthouse Graphql belongsToMany mutation with pivot values - laravel

I have some Laravel models that are related via a pivot table in a belongsToMany relation.
Now I try to create a mutation in Laravel Lighthouse to join the models and also fill the pivot values.
Somehow I cannot find out how to do this.
The Course model looks like this:
class Course extends Model
{
public function programs(): BelongsToMany
{
return $this->belongsToMany(\App\Models\Program::class, 'Course_Program')
->withPivot('id', 'year_id')
->withTimestamps();
}
}
My GraphQL code looks like this:
type Mutation {
createCourse(input: CreateCourseInput! #spread): Course! #create
}
type Course {
id: ID!
name: String!
programs: [ProgramWithPivot!]! #belongsToMany
}
type Program {
id: ID!
name: String!
}
type ProgramWithPivot {
id: ID!
name: String!
year_id: ID!
}
input CreateCourseInput {
name: String!
programs: CreateCourseProgramsRelation!
}
input CreateCourseProgramsRelation {
create: [CreateCourseProgramInput!]
}
input CreateCourseProgramInput {
id: ID!
year_id: ID!
}
The problem is that when I try to create programs in my course like this:
mutation {
createCourse(input: {
name: "new cours"
programs: {
create: [{
id: 1
year_id: 2
}]
}
}) {
id
programs {
id
year_id
}
}
}
Laravel Lighthouse tries to insert data into the Program table (and complains that Program.name does not have a default value).
However, I want to insert data (course_id, year_id and program_id) into the Course_Program table.
How do I tell Laravel Lighthouse to insert data in the pivot-table?

Mutating pivot data is currently not there, but I did a PR for this last week. You can follow the PR progress
In general it will be included either the way I did it, or maybe a little bit different. It was discussed in this issue
For now you can update your pivot data only using custom resolver/directive. But probably the best way will be just to wait till PR gets merged.

There is any way of doing update / upsert to pivot data via nested mutation?

Related

GraphQL query with multiple nested resolvers and mapping fields to arguments

From GraphQL Client's perspective, how do I perform a query with multiple nested resolvers where the fields from the parent are passed as arguments to the child resolver?
Here is a minimal example:
GraphQL Schema:
type Author {
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
releaseDate: String!
}
type Query {
// Returns a list of Authors ordered by name, 'first' indicates how many entries to return
getAllAuthors(first: Int!): [Author]!
// Returns a list of Books ordered by releaseDate, 'first' indicates how many entries to return
getBooksByAuthorId(first: Int! authorId: ID!): [Book]!
}
Is it possible to write a query to get all authors and their last released book? Something around the lines:
query GetAuthorsWithLastBook($first: Int!) {
getAllAuthors(first: $first) {
authorId: id
name
lastBook: getBooksByAuthor(1, authorId) {
title
}
}
}
In the example above, I attempted to alias getAllAuthors.id as authorId and pass the alias down as argument to getBooksByAuthor(...) but that didn't work.
The key aspect of the problem is that I don't know the authorIds beforehand. I could fetch the authors first and build a query to fetch their last book but that will result in multiple queries and that is something I would like to avoid.
Update
A Java Kickstarter example is available here: https://www.graphql-java-kickstart.com/tools/schema-definition/
yes, on the graphql definition, you need to add lastBook in the Author
type Author {
id: ID!
name: String!
lastBook: [Book]
}
Next up u need to write the resolver for the lastBook
const resolvers = {
Query: {
Author {
lastBook: (parent, args) {
const userId = parent.id;
return getBooksByAuthor(userId, 1);
},
}
}
};

BelongsToMany create operation with Pivot Data - PHP Lighthouse Laravel

In PHP Lighthouse you can have ManyToMany relationships. Using nested operations allows you to create say an Author, Post and connect them using a pivot table post_author ALL in one operation.
Lighthouse also allows you to store data in the pivot table. In their docs, they give an example of how to connect a record with some pivot table data. There is no example available on how to do a create operation with extra pivot table data.
Docs pivot data update operation: https://lighthouse-php.com/master/eloquent/nested-mutations.html#storing-pivot-data
type Mutation {
createPost(input: CreatePostInput! #spread): Post #create
}
input CreatePostInput {
title: String!
authors: CreateAuthorBelongsToMany
}
input CreateAuthorBelongsToMany {
create: [CreateAuthorInput!]
}
input CreateAuthorInput {
name: String!
#contribution_percentage: Int! #Pivot table column
}
#query:
mutation {
createPost(
input: {
title: "My new Post"
authors: {
create: [{ name: "Herbert", contribution_percentage: 50 }]
}
}
) {
id
authors {
name
}
}
}
I tried fiddling with the scheme to "guess" the correct scheme, to no avail.
My objective is: Create an Author, Post, and connect them using a post_author pivot table WITH extra pivot data, ALL in one operation.

FaunaDB - How to bulk update list of entries within single graphQL mutation?

I want to bulk update list of entries with graphQL mutation in faunaDB.
The input data is list of coronavirus cases from external source. It will be updated frequently. The mutation should update existing entries if the entry name is present in collectio and create new ones if not present.
Current GRAPHQL MUTATION
mutation UpdateList($data: ListInput!) {
updateList(id: "260351229231628818", data: $data) {
title
cities {
data {
name
infected
}
}
}
}
GRAPHQL VARIABLES
{
"data": {
"title": "COVID-19",
"cities": {
"create": [
{
"id": 22,
"name": "Warsaw",
"location": {
"create": {
"lat": 52.229832,
"lng": 21.011689
}
},
"deaths": 0,
"cured": 0,
"infected": 37,
"type": "ACTIVE",
"created_timestamp": 1583671445,
"last_modified_timestamp": 1584389018
}
]
}
}
}
SCHEMA
type cityEntry {
id: Int!
name: String!
deaths: Int!
cured: Int!
infected: Int!
type: String!
created_timestamp: Int!
last_modified_timestamp: Int!
location: LatLng!
list: List
}
type LatLng {
lat: Float!
lng: Float!
}
type List {
title: String!
cities: [cityEntry] #relation
}
type Query {
items: [cityEntry!]
allCities: [cityEntry!]
cityEntriesByDeathFlag(deaths: Int!): [cityEntry!]
cityEntriesByCuredFlag(cured: Int!): [cityEntry!]
allLists: [List!]
}
Everytime the mutation runs it creates new duplicates.
What is the best way to update the list within single mutation?
my apologies for the delay, I wasn't sure exactly what the missing information was hence why I commented first :).
The Schema
An example of a part of a schema that has arguments:
type Mutation {
register(email: String!, password: String!): Account! #resolver
login(email: String!, password: String!): String! #resolver
}
When such a schema is imported in FaunaDB there will be placeholder functions provided.
The UDF parameters
As you can see all the function does is Abort with the message that the function still has to be implemented. The implementation starts with a Lambda that takes arguments and those arguments have to match what you defined in the resolver.
Query(Lambda(['email', 'password'],
... function body ...
))
Using the arguments is done with Var, that means Var('email') or Var('password') in this case. For example, in my specific case we would use the email that was passed in to get an account by email and use the password to pass on to the Login function which will return a secret (the reason I do the select here is that the return value for a GraphQL resolver has to be a valid GraphQL result (e.g. plain JSON
Query(Lambda(['email', 'password'],
Select(
['secret'],
Login(Match(Index('accountsByEmail'), Var('email')), {
password: Var('password')
})
)
))
Calling the UDF resolver via GraphQL
Finally, how to pass parameters when calling it? That should be clear from the GraphQL playground as it will provide you with the docs and autocompletion. For example, this is what the auto-generated GraphQL docs tell me after my schema import:
Which means we can call it as follows:
mutation CallLogin {
login (
email: "<some email>"
password: "<some pword>"
)
}
Bulk updates
For bulk updates, you can also pass a list of values to the User Defined Function (UDF). Let's say we would want to group a number of accounts together in a specific team via the UI and therefore want to update multiple accounts at the same time.
The mutation in our Schema could look as follows (ID's in GraphQL are similar to Strings)
type Mutation { updateAccounts(accountRefs: [ID]): [ID]! #resolver }
We could then call the mutation by providing in the id's that we receive from FaunaDB (the string, not the Ref in case you are mixing FQL and GraphQL, if you only use GraphQL, don't worry about it).
mutation {
updateAccounts(accountRefs: ["265317328423485952", "265317336075993600"] )
}
Just like before, we will have to fill in the User Defined Function that was generated by FaunaDB. A skeleton function that just takes in the array and returns it would look like:
Query(Lambda(['arr'],
Var('arr')
))
Some people might have seen an easier syntax and would be tempted to use this:
Query(Lambda(arr => arr))
However, this currently does not work with GraphQL when passing in arrays, it's a known issue that will be fixed.
The next step is to actually loop over the array. FQL is not declarative and draws inspiration from functional languages which means you would do that just by using a 'map' or a 'foreach'
Query(Lambda(["accountArray"],
Map(Var("accountArray"),
Lambda("account", Var("account")))
))
We now loop over the list but don't do anything with it yet since we just return the account in the map's body. We will now update the account and just set a value 'teamName' on there. For that we need the Update function which takes a FaunaDB Reference. GraphQL sends us strings and not references so we need to transform these ID strings to a reference with Ref as follows:
Ref(Collection('Account'), Var("account"))
If we put it all together we can add an extra attribute to a list of accounts ids as follows:
Query(Lambda(["accountArray"],
Map(Var("accountArray"),
Lambda("account",
Do(
Update(
Ref(Collection('Account'), Var("account")),
{ data: { teamName: "Awesome live-coders" } }
),
Var("account")
)
)
)
))
At the end of the Map, we just return the ID of the account again with Var("account") in order to return something that is just plain JSON, else we would be returning FaunaDB Refs which are more than just JSON and will not be accepted by the GraphQL call.
Passing in more complex types.
Sometimes you want to pass in more complex types. Let's say we have a simple todo schema.
type Todo {
title: String!
completed: Boolean!
}
And we want to set the completed value of a list of todos with specific titles to true. We can see in the extended schema generated by FaunaDB that there is a TodoInput.
If you see that extended schema you might think, "Hey that's exactly what I need!" but you can't access it when you write your mutations since you do not have that part of the schema at creation time and therefore can't just write:
type Mutation { updateTodos(todos: [TodoInput]): Boolean! #resolver }
As it will return the following error.
However, we can just add it to the schema ourselves. Fauna will just accept that you already wrote it and not override it (make sure that you keep the required fields, else your generated 'createTodo' mutation won't work anymore).
type Todo {
title: String!
completed: Boolean!
}
input TodoInput {
title: String!
completed: Boolean!
}
type Mutation { updateTodos(todos: [TodoInput]): Boolean! #resolver }
Which means that I can now write:
mutation {
updateTodos(todos: [{title: "test", completed: true}])
}
and dive into the FQL function to do things with this input.
Or if you want to include the ID along with data you can define a new type.
input TodoUpdateInput {
id: ID!
title: String!
completed: Boolean!
}
type Mutation { updateTodos(todos: [TodoUpdateInput]): Boolean! #resolver }
Once you get the hang of it and want to learn more about FQL (that's a whole different topic) we are currently writing a series of articles along with code for which the first one appeared here: https://css-tricks.com/rethinking-twitter-as-a-serverless-app/ which is probably a good gentle introduction.

How to reshape a GraphQL (via Hasura) query response?

I have a CHAT_MESSAGE_FRAGMENT that returns all the message data from my Hasura graphql api.
However, the Gifted Chat react-native component requires the data in a specific structure so I'm attempting to convert it with the query below.
I'm able to alias all the top level data but can't figure out how to add a nested level of data.
I'm guessing it isn't possible but I thought I'd ask in case I'm missing something.
const GIFTED_CHAT_GROUP_MESSAGES_QUERY = gql`
query chatGroupMessages($chatGroupId: Int!) {
chat_message(
where: { to: { id: { _eq: $chatGroupId } } }
) {
_id: id,
# user: {
# _id: from.id, <== How do I add
# name: from.name, <== this secondary level?
# },
text: message,
image: image_url,
createdAt: created_at,
system: message_type,
}
}
${CHAT_MESSAGE_FRAGMENT}
`;
Assuming you already have chat_message.user_id -> users.id foreign key constraint set up, you'll also need to alias the from object in addition aliasing any of its nested fields:
const GIFTED_CHAT_GROUP_MESSAGES_QUERY = gql`
query chatGroupMessages($chatGroupId: Int!) {
chat_message(
where: { to: { id: { _eq: $chatGroupId } } }
) {
_id: id,
from: user: {
_id: id,
name
},
text: message,
image: image_url,
createdAt: created_at,
system: message_type,
}
}
${CHAT_MESSAGE_FRAGMENT}
`;
The secondary level of data is basically nested object queries in Hasura. You can nest any number of queries as long as a relationship has been created.
In this case, assuming the chat_message table has a user_id field, you can establish a foreign key constraint for chat_message.user_id -> users.id, where users is a table with id as primary key.
Once the foreign key constraint is created, Hasura Console automatically suggests relationships. Here user would be an object relationship in chat_message table.
Here's the official docs link for Creating a relationship

Prisma cursor based pagination

Just finished the excellent tutorial on building Prisma based graphql backend. They explain how to implement first-offset pagination here https://www.howtographql.com/graphql-js/8-filtering-pagination-and-sorting/.
Now I am wondering, how to implement cursor-based pagination?
Here are my types (they are the same as in tutorial):
type User
implements Node {
id: ID!
name: String!
email: String!
password: String!
links(...): [Link!]
votes(...): [Vote!]
}
type Link
implements Node {
id: ID!
createdAt: DateTime!
description: String!
url: String!
postedBy(...): User
votes(...): [Vote!]
}
In the playground I am trying to query user information along with the links, created by the user:
{
user(where: {id:"cjimzqrshb3nf0c29z1p7km0j"}) {
email
links {
id
url
description
}
}
}
It gives me back all the links, created by the user. How can I paginate them? Links object does not have paging information while linksConnection does not fit inside user object.
You can query the linksConnection related to the user, and therefore access cursors :
{
linksConnection(where:{user:{id:"cjimzqrshb3nf0c29z1p7km0j"}}){
pageInfo{
endCursor
startCursor
}
edges{
cursor
}
}
}
You can implement cursor based pagination with Prisma like this:
{
users{
links(first: 10, after:"some-id") {
description
}
}
}
This is possible because the id and the cursor is the same.
Alternatively you can use offset based pagination like this:
{
users{
links(first: 10, skip: 30) {
description
}
}
}

Resources