graphql - use queries in mutations - create a nested object - nested

I have a very simple model with post that embeds several comments
I wondered how I should do a mutation to add a new comment to the post
As I already have queries defined to get back a postwith a given id, I wanted to try to have the following mutation syntax working
mutation {
post(id: "57334cdcb6d7fb172d4680bb") {
addComment(data: {
text: "test comment"
})
}
}
but I can't seem to find a way to make it work. Even if I'm in a mutation, output type being a post addComment is seen as a field post should have.
Do you guys have any idea ?
Thanks

You can't embed fields into other fields like that.
You would create a new input object for your post mutation
input CommentInput {
text: String
}
type Mutation {
post(id: ID!, addComment: CommentInput): Post
}
In your resolver you look for the addComment variable and call the addComment resolver with the arguments.
Your mutation would be
mutation {
post(id: "57334cdcb6d7fb172d4680bb",
addComment: {
text: "test comment"
}) {
id
comment {
text
}
}
}

I could be wrong but you may want to just create a separate updatePost mutation that accepts the post id as an argument
type Post {
comments: [ Comment ]
}
input PostInput {
comments: [ CommentInput ]
}
type Mutation {
updatePost( id: ID!, input: PostInput ): Post
}
The updatePost mutation here takes the id of the post and the updated post object as arguments and returns a type Post.
So I would use this like so:
mutation {
updatePost( id: '1234', input: { comments: []) {
id
}
}
Hope this helps!

Maybe you could create addComment mutation that you pass post id to and then return a Post.
type Mutation {
addComment( postId: ID!, input: CommentInput ): Post
}

Related

Contentful NodeJs SDK, Client.getEntries | Load nested ContentType fields

I have the following structure
User {
image: Asset
...
}
Comment {
author: User
...
}
BlogArticle {
slug: Text
author: User
comments: Comment[]
}
When I pull entries with the following method
const articles = await client.getEntries({ content_type: "BlogArticle" })
console.log(articles.entries.fields.comments)
I only get the sys property for the author
[
{
author: {
sys: {
...
}
fields ??????
}
}
]
PS: This is the case for all types that come in second level of nesting
I checked the docs and the apis but with no luck
Any help ?
I created a similar content model and was able to get the fields of the Author successfully. One thing you can do is use the include parameter. With the include parameter, your code should look as follow:
const articles = await client.getEntries({ content_type: "BlogArticle", include: 2 })
console.log(articles.entries.fields.comments)
You can learn more about it here

Wrong data from client passes GraphQL validation

I've made simple CRUD app with React and Apollo client on NestJS server with GraphQL API.
I have this simple Mutations:
schema.gql:
type Mutation {
createUser(input: CreateUserInput!): User! // CreateUserInput type you can see in user.input.ts below
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): User!
}
user.input.ts:
import { InputType, Field } from "#nestjs/graphql";
import { EmailScalar } from "../email.scalar-type";
#InputType()
export class CreateUserInput {
// EmailScalar is a custom Scalar GraphQL Type that i took from the internet and it worked well
#Field(() => EmailScalar)
readonly email: string;
#Field()
readonly name: string;
}
"EmailScalar" type checks if "email" input has *#*.* format basically
And when i make createUser Query to GraphQL API like this:
It cannot pass validation
(because Email type works fine)
But when Query sent from client - it passes validation:
NestJS server log (from code below)
users.resolver.ts:
#Mutation(() => User)
async createUser(#Args('input') input: CreateUserInput) { // Type from user.input.ts
Logger.log(input); // log from screenshot, so if it's here it passed validation
return this.usersService.create(input); // usersService makes requests to MongoDB
}
And it gets into MongoDB
Here is client side part:
App.tsx:
...
// CreateUserInput class is not imported to App.tsx (it is at server part) but it seems to be fine with it
const ADD_USER = gql`
mutation AddMutation($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`
function App(props: any) {
const { loading, error, data } = useQuery(GET_USERS);
const [addUser] = useMutation(
ADD_USER,
{
update: (cache: any, { data: { createUser } }: any) => {
const { users } = cache.readQuery({ query: GET_USERS });
cache.writeQuery({
query: GET_USERS,
data: {
users: [createUser, ...users],
},
})
}
}
);
...
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return <UserTable users={data.users} addUser={addUser} updateUser={updateUser} deleteUser={deleteUser} />;
}
Can someone please explain to me, how does client Query passes validation and what have i done wrong?
Even two empty strings can pass through.
Never worked with NestJS, Apollo, React or GraphQL before, so I'm kinda lost.
For full code:
https://github.com/N238635/nest-react-crud-test
This is how your custom scalar's methods are defined:
parseValue(value: string): string {
return value;
}
serialize(value: string): string {
return value;
}
parseLiteral(ast: ValueNode): string {
if (ast.kind !== Kind.STRING) {
throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
}
// Regex taken from: http://stackoverflow.com/a/46181/761555
var re = /^([\w-]+(?:\.[\w-]+)*)#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (!re.test(ast.value)) {
throw new GraphQLError('Query error: Not a valid Email', [ast]);
}
return ast.value;
}
parseLiteral is called when parsing literal values inside the query (i.e. literal strings wrapped in double quotes). parseValue is called when parsing variable values. When your client sends the query, it sends the value as a variable, not as a literal value. So parseValue is used instead of parseLiteral. But your parseValue does not do any kind of validation -- you just return the value as-is. You need to implement the validation logic in both methods.
It would also be a good idea to implement the serialize method so that your scalar can be used for both input and response validation.

GraphQL Resolver Problems

I've spent quite a bit of time reading through the GraphQL tutorials but unfortunately they don't seem to cover things in quite enough depth for me to get my head around. I'd really appreciate some help with this real world example.
In the examples the queries are placed at the root of the resolver object; I can get this to work fine for single level queries. When I attempt to resolve a nested query however the nested resolver never gets called. What I'm massively confused by is every tutorial I find that isn't issued on the graphql website put in a Query object and nest their queries underneeth that, not root level.
Consider the following Schema:
type Product {
id: String!
retailerId: String!
title: String!
description: String
price: String!
currency: String!
}
type OrderLine {
product: Product!
quantity: Int!
}
type Order {
id: String!
retailerId: String!
orderDate: Date!
orderLines: [OrderLine!]!
}
type Query {
product(id: String!): Product
order(id: String!): Order
}
schema {
query: Query
}
And the following query:
query {
order(id: "1") {
id
orderLines {
quantity
}
}
}
I have tried multiple versions of implementing the resolvers (just test data for now) and none seem to return what I exect. This is my current resolver implementation:
const resolvers = {
OrderLine: {
quantity: () => 1,
},
Order: {
orderLines: (parent: any, args: any) => { console.log("Calling order lines"); return []; },
},
Query: {
product(parent, args, ctx, other) {
return { id: args.id.toString(), test: true };
},
order: ({ id }) => { console.log("Calling order 1"); return { id: id.toString(), testOrder: true, orderLines: [] }; },
},
order: ({ id }) => { console.log("Calling order 2"); return { id: id.toString(), testOrder: true, orderLines: [] }; },
};
In the console I can oberse the "Calling order 2" log message, there are no logs to "Calling order lines" and the order lines array is empty.
So two part question:
1) Why does it hit "Calling order 2" and not "Calling order 1" in the above example?
2) Why won't the above work for the nested query Order.OrderLines?
Thanks in advance!
In query
type Query {
product(id: String!): Product
order(id: String!): Order
users: User
}
schema {
query: Query
}
In resolvers
const resolvers = {
order: ({ id }) => function
product: ({ id }) => function
}
Graphql work on query resolver concept. If you want to any query(example users) you must have
resolver(ie users) which return User having definition in type User.
Graphql query is interactive and case sensitive
The next step is to implement the resolver function for the order/product query.
In fact, one thing we haven’t mentioned yet is that not only root fields,
but virtually all fields on the types in a GraphQL schema have resolver functions.
1) Why does it hit "Calling order 2" and not "Calling order 1" in the above example?
In this Query
query {
order(id: "1") {
id
orderLines {
quantity
}
}
}
then it go to order which return Order with define type
2) Why won't the above work for the nested query Order.OrderLines?
You can only use two query first order and second product only as per your schema
Please check doc for nested query for this requirement.
If you use buildSchema to generate your schema, the only way to provide resolvers for your fields is through the root object. But this is more of a hack -- you're not actually overriding the default resolvers for the fields and as such, you're basically limited to just working with the root-level fields (as you are learning the hard way). This is why only the Query.order function is called -- this is a root-level field. Why passing functions through the root (kind of) works is explained in detail here.
The bottom line is you shouldn't be using buildSchema. If you want to use SDL to define your schema, migrate to using Apollo Server.

Understanding GraphQl query

I was trying to learn and comprehend GraphQl.
In order to do so, I went to apollo-graphQL blog and started with getting started launch
From their blogs, in our schema.js file, consider we have something like
onst { gql } = require('apollo-server');
const typeDefs = gql`
type Query {
launches: [Launch]!
launch(id: ID!): Launch
me: User
}
type Launch {
id: ID!
site: String
mission: Mission
rocket: Rocket
isBooked: Boolean!
}
module.exports = typeDefs;
Now in tool from where we can query (like graphiqL), there in their example they have done something like this in query
{
launch(id: 1) {
site
}
}
I am unsure- here about the place our site in the above graphiqL object is coming and how can we write it (since in our query, launch is expecting a return type if Launch and only want id launch(id: ID!): Launch)
Why is this query invalid
{
launch(id: 1)
}
You need to specify fields for complex types. For your example ("and only want id").
{
launch(id: 1) {
id
}
}
What goes in (id: 1) is an input for the query (like an argument for a function). But you still have to specify what you want back.
UPD. Just to be clear the same rule applies to nested complex types. For example, if you want to get launch rocket as well you can't simply do
{
launch(id: 1) {
id
rocket # does not work
}
}
You need to specify which rocket fields you want
{
launch(id: 1) {
id
rocket {
id
}
}
}

How to return both error and data in a graphql resolver?

I was thinking about ways of implementing graphql response that would contain both an error and data.
Is it possible to do so without creating a type that would contain error?
e.g.
Mutation addMembersToTeam(membersIds: [ID!]! teamId: ID!): [Member] adds members to some team. Suppose this mutation is called with the following membersIds: [1, 2, 3].
Members with ids 1 and 2 are already in the team, so an error must be thrown that these members cannot be added, but member with an id 3 should be added as he is not in the team.
I was thinking about using formatResponse but seems that I can't get an error there.
Is it possible to solve this problem without adding error field to the return type?
Is it possible to solve this problem without adding error field to the return type?
Unfortunately, no.
A resolver can either return data, or return null and throw an error. It cannot do both. To clarify, it is possible to get a partial response and some errors. A simple example:
const typeDefs = `
type Query {
foo: Foo
}
type Foo {
a: String
b: String
}
`
const resolvers = {
Query: {
foo: () => {},
}
Foo: {
a: () => 'A',
b: () => new Error('Oops!'),
}
}
In this example, querying both fields on foo will result in the following response:
{
"data": {
"foo": {
"a": "A",
"b": null
}
},
"errors": [
{
"message": "Oops",
"locations": [
{
"line": 6,
"column": 5
}
],
"path": [
"foo",
"b"
]
}
]
}
In this way, it's possible to send back both data and errors. But you cannot do so for the same field, like in your question. There's a couple of ways around this. As you point out, you could return the errors as part of the response, which is usually how this is done. You could then use formatResponse, walk the resulting data, extract any errors and combine them with them with any other GraphQL errors. Not optimal, but it may get you the behavior you're looking for.
Another alternative is to modify the mutation so it takes a single memberId. You can then request a separate mutation for each id you're adding:
add1: addMemberToTeam(memberId: $memberId1 teamId: $teamId): {
id
}
add2: addMemberToTeam(memberId: $memberId2 teamId: $teamId): {
id
}
add3: addMemberToTeam(memberId: $memberId3 teamId: $teamId): {
id
}
This can be trickier to handle client-side, and is of course less efficient, but again might get you the expected behavior.
If you think about combining the GraphQL error - there is a way to do it in Apollo.
You need to set errorPolicy to all. That will help you notify users about the error and at the same time have as much data as possible.
none: This is the default policy to match how Apollo Client 1.0
worked. Any GraphQL Errors are treated the same as network errors and
any data is ignored from the response.
ignore: Ignore allows you to
read any data that is returned alongside GraphQL Errors, but doesn’t
save the errors or report them to your UI.
all: Using the all policy
is the best way to notify your users of potential issues while still
showing as much data as possible from your server. It saves both data
and errors into the Apollo Cache so your UI can use them.
But according to best practices, you shouldn't manipulate it in this way.
This is a great article about handling errors in GraphQL.
So, preferable way is to add "errors" field as part of your response and handle it in JS code.
We can achieve this by using a union. I would recommend visiting the great article Handling GraphQL errors like a champ
Example:
Mutation part: We can return the union type for the response & capture the result according to types.
type MemberType {
id: ID!
name: String!
}
enum ErrorType {
BAD_REQUEST_ERROR
FORBIDDEN_ERROR
INTERNAL_SERVER_ERROR
NOT_FOUND_ERROR
UNAUTHORIZED_ERROR
}
type GraphqlError {
type: ErrorType!
code: String!
message: String!
helpLink: URL
}
union UserRegisterResult = MemberType | GraphqlError;
addMembersToTeam(membersIds: [ID!]! teamId: ID!): UserRegisterResult!
Response:
addMembersToTeam(membersIds: [ID!]! teamId: ID!): {
...on MemberType{
id,
name,
}
...on GraphqlError{
id,
message,
statusCode,
}
}

Resources