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.
Related
I just can't figure out the query and even if it's allowed to write a single query to push 4 different objects into 4 different arrays deeply nested inside the user Object.
I receive PATCH request from front-end which's body looks like this:
{
bodyweight: 80,
waist: 60,
biceps: 20,
benchpress: 50,
timestamp: 1645996168125
}
I want to create 4 Objects and push them into user's data in Mongo Atlas
{date:1645996168125, value:80} into user.stats.bodyweight <-array
{date:1645996168125, value:60} into user.stats.waist <-array
...etc
I am trying to figure out second argument for:
let user = await User.findOneAndUpdate({id:req.params.id}, ???)
But i am happy to update it with any other mongoose method if possible.
PS: I am not using _id given by mongoDB on purpose
You'll want to use the $push operator. It accepts paths as the field names, so you can specify a path to each of the arrays.
I assume the fields included in your request are fixed (the same four property names / arrays for every request)
let user = await User.findOneAndUpdate(
{ id: req.params.id },
{
$push: {
"stats.bodyweight": {
date: 1645996168125,
value: 80,
},
"stats.waist": {
date: 1645996168125,
value: 60,
},
// ...
},
}
);
If the fields are dynamic, use an object and if conditions, like this:
const update = {};
if ("bodyweight" in req.body) {
update["stats.bodyweight"] = {
date: 1645996168125,
value: 80,
};
}
// ...
let user = await User.findOneAndUpdate(
{ id: req.params.id },
{
$push: update,
}
);
The if condition is just to demonstrate the principle, you'll probably want to use stricter type checking / validation.
try this:
await User.findOneAndUpdate(
{id:req.params.id},
{$addToSet:
{"stats.bodyweight":{date:1645996168125, value:80} }
}
)
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
}
}
}
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,
}
}
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
}
So I've set up something really simple to learn how to use Loopback.
The models are as follows:
Person
- based on built in User model
food_pref
typeId (number)
personId (number)
food_type
type (string)
Relationships:
Person has many food_prefs (foreign key: personId)
food_pref belongs to Person (foreign key: personId)
food_pref belongs to food_type (foreign key: typeId)
An auto-generated method gets created that returns the food_prefs based on the id of the Person.
People/{id}/foodPrefs
This returns:
[
{
"typeId": 0,
"personId": 0,
"id": 0
}
]
What I want to do is add a separate remote method called "getPrefs" that returns the name of the type under food_type based on the typeId in food_pref.
So let's say typeId is 1 and id 1 in food_types is Italian Food then the remote method would return this:
{
"type": "Italian Food"
}
I was told to use Person.js and add something along these lines but I'm really confused about the include statement as well as what to do inside the brackets. Often it crashes with an error saying: Error: Relation "food_pref" is not defined for Person model, see what they recommended below:
module.exports = function(Person) {
Person.getPrefs = function(personId, cb) {
Person.findById(personId, { include: { food_pref: "food_type" } }, function(err, user) {
if (err) throw err;
});
}
Person.remoteMethod (
'getPrefs',
{
http: {path: '/getPrefs', verb: 'get'},
accepts: {arg: 'personId', type: 'number', http: { source: 'query' } },
returns: {arg: 'type', type: 'string'}
}
);
};
What am I doing wrong here?
Edit:
According to strongloop documentation when you define a personal remote method, strongloop automatically provides a callback that will returns datas if needed.
See below updated code
You want to include the food_pref relation as well as the food_type realation inside this food_pref. Get this into your getPrefs custom method:
Person.getPrefs = function(personId, cb) {
Person.findById(personId, {
include: [{
relation: 'food_pref',
scope: {
include: {
relation: 'food_type'
}}}
]},
function(err, personFound) {
if (err)
console.log(err);
else {
cb(null, personFound)
}
});
};
What it does: your personal method is called with the personId argument and a cb argument (automatically passed by strongloop !). Your method finds the right person by id, includes the relations (name of the type of food as well), then when the result has been fetched, the callback inside "Person.findById" calls the callback 'cb' with datas fetched (here personFound)
Person.remoteMethod(
'getPrefs', {
http: {path: '/:personId/getPrefs', verb: 'get'},
accepts: [{arg: 'personId', type: 'number'}],
returns: {arg: 'person', type: 'object'},
description: ['a person object']
}
);
Then the returned object should contain the name of the type of food.
Make sure you include the right relation names you have in your Person.json and so on.
If you just want the name of food pref, follow the same idea with different methods:
just send the string inside your object to the automatic callback argument 'cb' (and modify the registration to announce the kind of return your method sends)
search directly inside food_type table with a "where" condition (where personId = the id of the person you are looking for)
Take a look at the link at the link to strongloop doc as it is pretty and detailed regarding remote methods.
Hope it helped.