How to implement GraphQL fragments in schema? - graphql

I try to use fragments in GraphQL
There is my schema
user/getUser.gql
input GetUserByUsernameInput {
username: String!
}
fragment UserFragment on User {
username
information
pictureUrl
}
type UserType {
...UserFragment
}
type GetUserByUsernameResponse {
user: UserType
errors: [Error]!
}
type Query {
getUserByUsername(input: GetUserByUsernameInput!): GetUserByUsernameResponse!
}
shared/User.gql
type User {
id: Int!
username: String!
email: String!
information: String
pictureUrl: String
}
And I got dummy error
(node:26206) UnhandledPromiseRejectionWarning: Syntax Error: Expected Name, found ...
GraphQL request (12:9)
11: type UserType {
12: user: ...UserFragment
^
13: }
How should my code looks like to use GraphQL fragments?
PS. I use graphql-code-generator

Fragments only exist in queries; you can't use them in the schema language.
In your example the response should include a reference to the User that's being returned:
type GetUserByUsernameResponse {
user: User
errors: [Error]!
}
When you make a query, though, you can include the fragment.
query WhoAreYou($username: String!) {
getUserByUsername(input: {username: $username}) {
user { ...UserFragment }
errors { message }
}
}
fragment UserFragment on User {
username
information
pictureUrl
}
Fragments tend to be somewhat more useful if either you have reason to request the same fields on the same type of object multiple times in one query, or if you have a client library that's capable of picking fragments out of a "library" of GraphQL queries you'll use multiple places. (For example, the Ruby graphql-client gem can be configured with a file of many queries, and you can make a specific query out of it by operation name and it will make a query with that operation and any fragments it references, so you can reuse the same fragments across several queries.)

Related

Querying all objects in Amplify mock api and getting null

I am using Amplify in a simple use case to mock an existing frontend. I have a cutdown schema.graphql as follows:
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }
schema {
query: Query
}
type Query {
getAirports: [Airport]
}
type Airport #model {
id: Int! #primaryKey
code: String!
city: String!
country: String!
}
The getAirports query is intended to return all the airports. I run amplify mock api and it generates all the resolvers.
When I navigate to http://localhost:20002, I can see the option to use getAirports, however it returns null even when data is present in the mocked database. The response is
{"data":null,"errors":[{"message":"Cannot return null for non-nullable field Query.getAirports.","locations":[{"line":2,"column":3}],"path":["getAirports"]}]}
I'm curious how I can write the schema to have a getAirports query in a way that it returns data a full list of Airports similar to listAirports which is created by default.

GraphQL variable combination validation

Imagine the following (simplified) GraphQL schema:
type User {
username: String
email: String
}
type Query {
user(username: String, email: String): User
}
If I would only want to allow querying user by giving a username, I would of course change it to user(username: String!), making the username required using the exclamation mark. Same thing vice versa with the email.
Is it possible though to have a GraphQL native solution where I validate for the existence of only either one (logical XOR) or at least one (logical OR) of the two input parameters?
Of course I could do it in the query resolver manually, but a #constraint directive like it is being used in Apollo GraphQL spreading across variables would be nice.
Directly ... not possible:
check fields existence [within args] on resolver;
use #constraint to check each field shape;
Indirectly:
you can try to use union of input types:
.
type UserNameInput {
username: String!
}
type UserEmailInput {
email: String!
}
type UserInput = UserNameInput | UserEmailInput
type Query {
user(input: UserInput): User
}

How to document errors in graphql?

type Query {
"""
ErrorCode: EMAIL_DUPLICATED
type EmailDuplicatedError {
email: String!
source: UserSource!
}
enum UserSource {
Google
Facebook
Github
}
"""
register(email: String!, password: String!): AccessToken!
}
"""
The AccessToken scalar type is a string of 16 characters.
"""
scalar AccessToken
Hope you can get what I mean through the above schema. I'd like to know if there is any code generator can support errors documented this way, so I can reduce the code I write on both client and server side.
I don't want to define errors like the following
type Query {
register(email: String!, password: String!): RegisterResponse
}
type RegisterResponse {
accessToken: AccessToken
error: EmailDuplicatedError
}
type EmailDuplicatedError {
email: String!
source: UserSource!
}
enum UserSource {
Google
Facebook
Github
}
"""
The AccessToken scalar type is a string of 16 characters.
"""
scalar AccessToken
Because I'd like errors to be responded in errors field, and api only shows what you can get when you succeeded.
Thank you for your time reading this post.
There's some ways to do error handling with GraphQL, I'll recommend you two:
Using response extensions:
GraphQL JSON response.error has a field called extensions, you can use this to set a code field like:
{
"error": {
"errors": [
{
"message": "example",
"extensions": {
"code": "YOUR CODE"
}
}
]
}
}
Using unions:
There is an medium post by Sasha Solomon that talks about that:
https://sachee.medium.com/200-ok-error-handling-in-graphql-7ec869aec9bc
Using the examples in this post, the way to handle graphql errors with unions is like this:
type User {
id: ID!
name: String
}
type Suspended {
reason: String
}
type IsBlocked {
message: String
blockedByUser: User
}
type UnavailableInCountry {
countryCode: Int
message: String
}
"User, or reason we can't get one normally"
union UserResult = User | IsBlocked | Suspended
type Query {
user: UserResult!
}

Using nested arguments in GraphQL operations

I have a schema like so:
scalar Date
schema {
query: Query
}
type Query {
user(id: ID!): User
messages(userId: ID!): [ChatMessage!]!
}
type User {
id: ID!
username: String!
email: String!
}
type ChatMessage {
id: ID!
content: String!
time: Date!
user: User!
}
And I want to make an operation where you can get all messages for a user, but since User and ChatMessage are in separate database tables I would need to perform two queries (one to get the ChatMessages and one to get the User), so I thought I should model it like so:
query findMessagesForUser($userId: ID!) {
messages(userId: $userId) {
id
content
user(id: $userId) {
username
email
}
}
}
This returns a parse error on the schema:
GraphQLDocumentError: Unknown argument "id" on field "ChatMessage.user".
So, how do I get the $userId argument passed to the resolver for ChatMessage.user?
In your schema, you’ve defined an id input on your Query.user method. In your query, you are trying to supply an id to the Message.user property, however you haven't defined this input in your schema.
If you wanted to accept an id on ChatMessage.user, you'd need to define it as:
type ChatMessage {
id: ID!
content: String!
time: Date!
user(id: ID!): User
}
However, it wouldn't really make sense (at least to me) to construct a schema this way, I assume there's only one user (author) per message.
As indicated by #xadm, the object you resolved at the ChatMessage level will be passed into the user resolver as the first argument.
Even if you're not exposing ChatMessage.userId in the schema (that's fine), you'd still probably load this up in your back- end (the foreign key value in the ChatMessage table) and set this on the object used to resolve ChatMessage.
This way, you'll (lazy) load user IF that's included in the query, using the userId property of the parent ChatMessage object argument (remember you don't need to expose ChatMessage.userId via the schema, it's just on the object you use to resolve ChatMessage).
I'd consider modelling more like this (filter input used as an additional contrived example):
type Query {
user(id: ID!): User
messages(filter: MessageFilter): [ChatMessage!]!
}
type MessageFilter {
search: String
paging: PagingFilter
}
type PagingFilter {
after: ID!
pageSize: Int!
}
type User {
id: ID!
username: String!
email: String!
messages(filter: MessageFilter): [ChatMessage!]!
}
In your resolver map, you can wire up the same function to resolve messages at the User level and at the Query level. The only difference is you wouldn't have a userId at the Query level.
If consumers want to view/search messages from all users, they use the top level Query messages method.
{
messages({search: 'graphql'}) {
id,
content,
time
}
}
If the consumer wants to view/search one user's messages, go through the top level Query users method into messages.
{
user(id: 3) {
messages({search: 'graphql'}) {
id,
content,
time
}
}
}
The filter example is contrived, but could provide basic paging for loading of messages.
apollographql.com/docs/graphql-tools/resolvers

Enumerating all fields from a GraphQL query

Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.

Resources