Do I have to create a new type to define array of objects in GraphQL schemas? - graphql

I’m trying to replicate a REST API that I’ve built in the past and one part that got me thinking was if one of my tables had an array of objects. So for example, I have a table called Profile and it contains arrays Experience and Education that would strictly be under Profile but has its own fields as well but not its own table.
While I was adding fields in GraphQL, I bumped into this not really having a solid solution other than to create new types and then associating them with relationships and then have a resolver or a front-end make sure a Profile is created first before the Experience/Education portion is. I’m not sure if it’s the right way to do it or if there is a better way to do it. Down below is a snippet of what I ended up using… looking at the admin page, there are created tables for Profile, Experience and Education which is expected. But is there a way to only have just Profile and accomplish something similar? Or is this more of a way of life with GraphQL?
type Profile {
id: ID! #id
handle: String!
company: String
website: String
location: String
status: String!
githubUsername: String
experience: [Experience!] #relation(link: INLINE)
education: [Education!] #relation(link: INLINE)
}
type Experience {
id: ID! #id
title: String!
company: String!
}
type Education {
id: ID! #id
title: String!
company: String!
}

In Prisma, you can use embedded types. You would drop the #relation directive and add #embedded directives to the types you're embedding:
type Profile {
id: ID! #id
handle: String!
company: String
website: String
location: String
status: String!
githubUsername: String
experience: [Experience!]
education: [Education!]
}
type Experience #embedded {
title: String!
company: String!
}
type Education #embedded {
title: String!
company: String!
}
However, this only possible if you're using MongoDB for your database and there's some specific limitations listed in the docs when using embedded types.

Related

How to query a third party package trait in Laravel lighthouse

I read Laravel Lighthouse documentation and searched around the web I did not find how to query a trait in a third party package in Laravel.
I'm using the qirolab/laravel-reactions package, it has the reactionSummary() trait.
I was asking how can I add this relation in the lighthouse query?
type Post {
id: ID!
title: String!
excerpt: String!
image_url: String!
slug: String!
source: Source! #belongsTo
reactionSummary: ???????
created_at: DateTime!
updated_at: DateTime!
}
I have a purpose with my question beside solving my issue, understanding how lighthouse work with packages or how to integrate third party packages with lighthouse?
You need to define the schema for the reaction model in graphql and in the post schema you need to define an array of the reaction type.
According to the model (https://github.com/qirolab/laravel-reactions/blob/master/src/Models/Reaction.php) the reaction graphql schema would look something like this:
type Reaction {
reactBy: User! #belongsTo
type: String
reactable: Reactable! #morphTo
}
Your post schema would change to
type Post {
id: ID!
title: String!
excerpt: String!
image_url: String!
slug: String!
source: Source! #belongsTo
reactionSummary: [Reaction]
created_at: DateTime!
updated_at: DateTime!
}
As I can see from the migration file https://github.com/qirolab/laravel-reactions/blob/master/migrations/2018_07_10_000000_create_reactions_table.php. The reaction has a polymorphic relation.
That means the returning type can vary depending on which kind of model is set as the reactable_type in the field. Therefore you need to define your own Union type.
A Union is an abstract type that simply enumerates other Object Types. They are similar to interfaces in that they can return different types, but they can not have fields defined.
Source: https://lighthouse-php.com/5/the-basics/types.html#union
See also polymorphic relations and the union section: https://lighthouse-php.com/5/eloquent/polymorphic-relationships.html#one-to-one
I hope that gives you the direction on how you can proceed.

Using Apollo Federated GraphQL, is there a way to sort/paginate on a value from an external entity?

Is it possible to mark a field in an input to a query as requiring a field extended by a different service?
Using the federation-demo to illustrate my question, but keeping a little bit more simple. Having just an Account service and a Reviews service, and adding a karma field to the User, is it possible to filter reviews based on User karma.
Account service, adding a karma int to a User:
extend type Query {
me: User
}
type User #key(fields: "id") {
id: ID!
name: String
username: String
karma: Int!
}
Reviews service, adding a reviews Query:
extend type Query {
reviews(minKarma: Int): [Review!]!
}
type Review #key(fields: "id") {
id: ID!
body: String
author: User #provides(fields: "username")
product: Product
}
extend type User #key(fields: "id") {
id: ID! #external
username: String #external
karma: Int! #external
reviews: [Review]
}
extend type Product #key(fields: "upc") {
upc: String! #external
reviews: [Review]
}
In my resolver for Query.reviews, I want to filter out any review where review.author.karma is less than the specified minKarma.
How do I tell the gateway that when minKarma is specified in the Query input, I want the Account service to be queried first and a representation of users to be passed into the Reviews service, with the karma of each user attached to the review as the author, so that I can do the filter?
Circling back to the question at the top of this post, can I mark the minKarma field as requiring User.karma?
This is the questions plaguing me as well.

How to federate two apollo services that provide the same type

I am new to apollo and I have two apollo service that I want to federate by using apollo federation:
Productservice:
extend type Query {
job(id: String!): Job
}
type Seo {
title: String!
description: String!
keywords: String!
}
type Product #key(fields: "id") {
id: ID!
title: String!
seo: Seo!
}
StaffService:
extend type Query {
staffMember(id: String!): StaffMember
}
type Seo {
title: String!
description: String!
keywords: String!
}
type StaffMember #key(fields: "id") {
id: ID!
title: String!
seo: Seo!
}
How can I use the type Seo in response objects of both objects? Is the correct procedure to create an interface Seo and implement StaffMemberSeo and ProductSeo or is there an annotation that allows me to define the exactly same type within two services?
One service must own the type. In that service use the #key directive. In the referencing services use #extend, and include a stub of the type with fields used by that service.
Think of this as like a foreign key in a SQL database.

GraphQL data modelling - extended types (Prisma)

In my Prisma Data Model I started out with a basic User type like this:
type User {
name: String!
email: String! #unique
password: String!
}
Now a User can have two roles: either as a candidate or as a user associated with an employer. If a candidate, the user should also have a set of applications and a set of qualifications, if associated with an employer it should have an access level and a reference to the employer.
First off, is there any way to extend basic types in GraphQL data modelling? If so, how would I go about doing it?
If there is not, I can see three different methods used, and I'm curious what are the pros and cons of each approach:
Having two separate types CandidateUser and EmployerUser, each with the fields name, email, password. I see two problems with this approach: The #unique tag on email is not reliable, and I would have to write a custom verification to make sure the field is unique across both types; and having a single login-function that takes email and fetches the users corresponding data is no longer trivial: it needs to do a lookup in both tables.
Like this:
type CandidateUser {
name: String!
email: String! #unique
password: String!
applications: [Application!]!
qualifications: [Qualification!]!
}
type EmployerUser{
name: String!
email: String! #unique
password: String!
employer: Employer!
accessRight: AccessRight!
}
Again two separate types, but with a RootUser containing name, email and password, and with CandidateUser and EmployerUser each having a one-to-one reference to a RootUser. This would enforce the #unique tag on the email field, but lookup would still be nontrivial.
type RootUser{
name: String!
email: String! #unique
password: String!
}
type CandidateUser {
rootUser: RootUser!
applications: [Application!]!
qualifications: [Qualification!]!
}
type EmployerUser{
rootUser: RootUser!
employer: Employer!
accessRight: AccessRight!
}
Extending User to have the fields within EmployerUser and CandidateUser as optional parameters. This is a pretty simple approach, but I would need custom handling to enforce requiring fields (as in, I can not mark for instance employer as required as that field would not exist for a Candidate).
type User{
name: String!
email: String! #unique
password: String!
applications: [Application!]!
qualifications: [Qualification!]!
employer: Employer
accessRight: AccessRight
}
I really want to ask if there is a better way of solving this. I'm still pretty new to GraphQL and not the best data modeler to begin with, but I'd greatly appraciate any nudge in the right direction :)
And if I do not have any other choice but the three I listed, which one would make the most sense?
What you're trying to do is implementing an interface type:
An Interface is an abstract type that includes a certain set of fields that a type must include to implement the interface.
interface User {
name: String!
email: String! #unique
password: String!
}
This means that any type that implements User needs to have these exact fields, with these arguments and return types. So now your Candidate type can implement User:
type Candidate implements User {
name: String!
email: String! #unique
password: String!
applications: [Application!]!
qualifications: [Qualification!]!
}
Interfaces are useful when you want to return an object or set of objects, but those might be of several different types. Have a look at the interface abstract type documentation for more information.
Update:
Since this is a Prisma GraphQL question now, you should be aware that Prisma does not support Interfaces or Union Types as yet. Issue #83 and issue #165 discuss both respectively as feature requests.
However, there is this great article that discuss the workarounds for such approach:
GraphQL Interfaces (and Union Types) with Prisma and Yoga
Which boils down to 2 options:
Storing all data with optional type-specific fields under one type (the interface) in Prisma, and then splitting the data back between the primitive types in the app server.
Storing the data in each primitive type on Prisma, and stitching things for queries on the app server.

GraphQL Prisma - define "vote" type that links to two users

I've just started using Prisma. Before was mainly using firebase and mongodb to define my schemas.
I'm trying to define the following schema:
Vote {
id: ID!
from: User! # The user who voted
for: User! # The user that received a vote
rate: Float!
}
Basically, what I want to achieve is enable users to vote for other users (give them a score).
In, say, MongoDB I would do it by creating a separate collection like following:
{
id: DocumentID
from: String // id of the user who voted
for: String // id of the user that received a vote
rate: Number
}
In here I just specify those fields (from and for) as strings and after link them with the User collection by the application logic.
For sure, it's gonna be different in GraphQL Prisma. But I'm still a bit confused on how the relationships are built. And what really happens underneath.
How can I create such schema using Prisma GraphQL?
When there are more than one relational field to the same type, you need to use the #relation directive to make it unambiguous.
type Vote {
id: ID! #unique
votingUser: User! #relation(name: "VoteAuthor")
votedUser: User! #relation(name: "VoteReceiver")
rate: Float!
}
type User {
id: ID! #unique
receivedVotes: [Vote!]! #relation(name: "VoteReceiver")
givenVotes: [Vote!]! #relation(name: "VoteAuthor")
name: String!
}

Resources