I am experimenting with graphql and have created a simple server using graphql-yoga. My Mongoose product model queries my database and both resolvers return data as expected. So far it all works and I am very happy with how easy that was. However, I have one problem. I am trying to add a way to paginate the results from graphQL.
What did I try?
1) Adding a limit parameter to the Query type.
2) Accessing the parameter through args in the resolver
Expected behaviour
I can use the args.limit parameter in my resolver and use it to alter the Mongoose function
Actual behaviour
I can't read the arg object.
Full code below. How do I reach this goal?
import { GraphQLServer } from 'graphql-yoga'
import mongoose from "mongoose"
import {products} from "./models/products.js"
const connection = mongoose.connect('mongodb://myDB')
const prepare = (o) => {
o._id = o._id.toString()
return o
}
const typeDefs = `
type Product {
_id: String
name: String
description: String
main_image: String
images: [String]
}
type Query {
product(_id: String): Product
products(limit: Int): [Product]
}
`
const resolvers = {
Query: {
product: async (_id) => {
return (await products.findOne(_id))
},
products: async (args) => {
console.log(args.name)
return (await products.find({}).limit(args.limit))
},
},
}
const server = new GraphQLServer({
typeDefs,
resolvers
})
server.start(() => console.log('Server is running on localhost:4000'))
The arguments for a field are the second parameter passed to the resolver; the first parameter is the value the parent field resolved to (or the root value in the case of queries/mutations). So your resolvers should look more like this:
product: (root, { _id }) => {
return products.findOne(_id)
}
Related
I Have Created two File
index.js
const {ApolloServer,gql} = require('apollo-server');
const fs = require('fs');
const path = require('path');
const typedefs = gql`
type Query {
info: String!
ask: [Person!]
}
type Person {
name: String!
age: Int!
}
`;
const resolvers = {
Query: {
info: () => `Hello World from Linux Fan`,
ask: () => {
return [fs.readFileSync(__dirname+path.join('/db.db'),'utf-8')]
}
}
}
const server = new ApolloServer({
typedefs,
resolvers
}).listen().then(({url}) => console.log(url)).catch(err => console.log(err));
and one More File for storing Database
db.db
{
name:"Linux Age",
age: 19
}
But The Problem is everytime I make a query for fetching name and age like
{
info
ask{
name
}
}
There is a problem which exist and say
"Cannot return null for non-nullable field Person.name"
How to Solve ??
According to Node.js documentation (https://nodejs.org/api/fs.html#fs_fs_readfilesync_path_options), fs.readFileSync() returns a String or Buffer. From the schema, however, ask() returns an array of type Person which is an object. The result of fs.readFileSync() should be converted to object before returning:
ask: () => {
const person = JSON.parse(JSON.stringify(
fs.readFileSync(__dirname + path.join('/db.db'), 'utf-8').toString()
));
return [person];
}
Notice that I called JSON.stringify() before parsing it with JSON.parse(). The reason is the file db.db has a javascript object (keys, nanely name and age, without double quotes around them) and not a JSON object (keys with quotes as shown below):
{
"name":"Linux Age",
"age": 19
}
Otherwise, JSON.parse() would have a problem parsing the invalid JSON format:
{
name:"Linux Age",
age: 19
}
In addition, toString() after calling readFileSync() is needed to convert a Buffer to a string:
fs.readFileSync(__dirname + path.join('/db.db'), 'utf-8').toString()
I'm currently studying GraphQL and as part of the developing process, i'm interested with modularization of my code - i do understand how to write query, but fail to understand how to correctly implement query of queries.
That is the rootQuery.js
const {
GraphQLInt,
GraphQLList,
GraphQLObjectType,
GraphQLSchema,
GraphQLFloat,
GraphQLString
} = require("graphql");
const bankRootQuery = require('../graphql/queries/bank.queries')
const rootQuery = new GraphQLObjectType({
name: "rootQuery",
fields: {
bankRootQuery: { type: bankRootQuery, resolve: () => { console.log(bankRootQuery.resolve) } }
}
});
module.exports = new GraphQLSchema({
query: rootQuery
});
And here is the bankRootQuery.js:
const { GraphQLObjectType, GraphQLInt, GraphQLNonNull, GraphQLID, GraphQLList } = require("graphql");
const BankType = require('../types/bank.type');
const models = require('../../models/models_handler');
module.exports = new GraphQLObjectType({
name: "bankRootQuery",
fields: {
getbanks: {
type: new GraphQLList(BankType),
resolve: () => {
return models.getBanks()
}
},
getbankByID: {
type: BankType,
args: {
bankID: { name: "bankID", type: GraphQLInt }
},
resolve: (_, args) => {
if (!models.getBanks().has(args.bankID))
throw new Error(`Bank with ID ${args.bankID} doesn't exists`);
return models.getBank(args.bankID);}
}
}
});
Assining bankRootQuery to the scheme object instead of rootQuery works perfectly fine, but using the rootQuery yields with null result when querying using GraphiQL - The Documentation Explorer structure seems to be in proper manner, so i'm guessing the problem is with the resolve function, which i don't understand how to define correctly.
Here is the result when querying using GraphQL:
{
"data": {
"bankRootQuery": null
}
}
If a field resolves to null, then execution for that "branch" of the graph ends. Even if the field's type is an object type, none of the resolvers for its "children" fields will be called. Imagine if you had a field like user -- if the field resolves to null, then it makes no sense to try to resolve the user's name or email.
Your resolver for the bankRootQuery field just logs to the console. Because it doesn't have a return statement, its return value is undefined. A value of undefined is coerced into a null. Since the field resolved to null, execution halts.
If you want to return something other than null, then your resolver needs to return something -- even if it's just an empty object ({}). Then the resolvers for any "child" fields will work as expected.
In general, I would advise against nesting your queries like this -- just keep them at the root level. For additional details around how field resolution works, check out this post.
I'm testing mongoose queries, and I found this bizarre behaviour. I have a document with _id: "5de64b376c79643fa847e86b", and if I call the findById method the document is returned just fine. But if I call the find method on the collection, giving as args an array with the same ID value than nothing is returned, while I expect an array of one element being the document. To summarize:
mongooseModel.findById(mongoose.Types.ObjectId("5de64b376c79643fa847e86b")) // Works
mongooseModel.find({ _id: { $in: [mongoose.Types.ObjectId("5de64b376c79643fa847e86b")] } }) //Doesn't work
What's the difference between the two, and why the second one doesn't work?
EDIT: This is kinda the code to access that method.
I define a DataSource in the ApolloServer configuration
const app = new ApolloServer({
...
dataSources: () => ({
source: new SourceAPI(DocumentModel)
})
...
});
where SourceAPI is the DataSource class and DocumentModel is the mongoose model.
SourceAPI is defined like this
class SourceAPI {
async get(ids) {
return await DocumentModel.find({
_id: {
$in: ids
}
});
}
}
Now, inside the GraphQL resolver I finally call the API method to get the documents, like this
const findResolver = () =>
DocumentSchema.get("$findById").wrapResolve(next => async rp => {
let ids = [];
ids.push(mongoose.Types.ObjectId(rp.args._id));
return await rp.context.dataSources.source.get(ids);
});
where DocumentSchema is the GraphQL Schema for the Document Model generated using graphql-compose-mongoose package. The get("$findById") and wrapResolve methods are also from that package. What I do is using these methods to get the GraphQL query parameters and pass them to the API method (in this case I'm just grabbing an ID for test).
If I change the API method to something like this
async get(id) {
return await DocumentModel.findById(id);
}
and the resolver method to this
const findResolver = () =>
DocumentSchema.get("$findById").wrapResolve(next => async rp => {
return await rp.context.dataSources.source.get(mongoose.Types.ObjectId(rp.args._id));
});
everything works
$in is used to find items which holds an array, but _id is just a JSON key/value pair. So you cannot use $in. If your object looks like this then you can use $in
{
id: [ObjectId("5de64b376c79643fa847e86b"),
ObjectId("5de64b376c79643fa847e86b")]
}
I'm building an Apollo Server. I have one simple endpoint communicating with Mongo. There's a collection of announcements.
export const typeDefs = gql`
type Query {
announcements: [Announcement]
announcementsByAuthor(author: String!): [Announcement]
}
type Announcement {
_id: ID!
msg: String!
author: String!
title: String
}
`;
export const resolvers = {
Query: {
announcements: () => {
return new AnnouncementController().getAnnouncements();
},
announcementsByAuthor: (author: string) => {
console.log('RESOLVER: ', author);
return new AnnouncementController().getAnnouncementsByAuthor(author);
}
},
}
In my graphiql interface, the announcements query works correctly:
{
announcements {
msg
author
}
}
The announcementsByAuthor query does not seem to be accepting the string argument, either from a variable or when hardcoded into the query.
query($author: String!){
announcementsByAuthor(author: $author) {
msg
author
}
}
Variables:
{
"author":"Nate"
}
I've logged out from the resolver, and an empty string is being passed in, instead of the specified value for the author variable. I'm new to graphql and I'm hoping someone can enlighten me as to what I'm sure is a simple oversight.
Try this instead:
announcementsByAuthor: (doc, {author}) => {
I am using neo4j dB and I have set up apollo graphql server (using graphql-server-express). Lets say my schema has 3 types namely "Country", "State" and "People" where 1 country can have multiple states and 1 state can have multiple people.
//Sample schema.js
import { makeExecutableSchema } from 'graphql-tools';
import resolvers from './resolvers';
const typeDefs = `
type Country {
id: Int!
name: String
state: [State]
people: [People]
}
type State {
id: Int!
name: String
countryID: CountryID
people: [People]
}
type People {
id: Int!
name: String
SSN: String
stateid:StateID
countryid:CountryID
}
type Query {
Countries: [Country]
States: [State]
Peoples: [People]
}
schema {
query: Query
}
`;
export default makeExecutableSchema({
typeDefs: typeDefs,
resolvers,
});
So, how should I write my resolver function in resolver.js file such that it would help me to fetch the data properly from any of the above types ?
I tried to use the following query in resolver.js file (to query the Neo4j database using Cypher query language), but got the type error and i am unable to fix it.
//Sample resolver.js file.
let neo4j = require('neo4j-driver').v1;
let driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j",
"******"));
const resolver = {
Query: {
Countries(_, params) {
let session = driver.session();
let query = "MATCH (country:Country) RETURN country;"
return session.run(query, params)
.then( result => { return result.records.map(record => { return
record.get("country").properties })})
},
},
State:{
state(State) {
let session = driver.session(),
params = {countryid: Country.id},
query = `
MATCH (s:State-[:PRESENT]->(c:Country)
WHERE s.countryid = $countryid
RETURN s;
`
return session.run(query, params)
.then( result => { return result.records.map(record => { return
record.get("state").properties })})
},
},
};
export default resolver;