How should I write a resolver while using apollo graphql server backed by neo4j database? - node.js

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;

Related

How to fetch the data from database for resolver in graphql

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()

Sequelize upsert or create without PK

I'm unable to perform any kind of upsert or create within Sequelize (v: 6.9.0, PostGres dialect).
Using out-of-the-box id as PK, with a unique constraint on the name field. I've disabled timestamps because I don't need them, and upsert was complaining about them. I've tried manually defining the PK id, and allowing Sequelize to magically create it. Here's the current definition:
const schema = {
name: {
unique: true,
allowNull: false,
type: DataTypes.STRING,
}
};
class Pet extends Model { }
Pet.define = () => Pet.init(schema, { sequelize }, { timestamps: false });
Pet.buildCreate = (params) => new Promise((resolve, reject) => {
let options = {
defaults: params
, where: {
name: params.name
}
, returning: true
}
Pet.upsert(options)
.then((instance) => {
resolve(instance);
})
.catch(e => {
// message:'Cannot read property 'createdAt' of undefined'
console.log(`ERROR: ${e.message || e}`);
reject(e);
});
});
module.exports = Pet;
Upsert code:
// handled in separate async method, including here for clarity
sequelize.sync();
// later in code, after db sync
Pet.buildCreate({ name: 'Fido' });
In debugging, the options appear correct:
{
defaults: {
name: 'Fido'
},
returning:true,
where: {
name: 'Fido'
}
}
I've also tried findOrCreate and findCreateFind, they all return errors with variations of Cannot convert undefined or null to object.
I've tried including id: null with the params, exact same results.
The only way I've succeeded is by providing PK in the params, but that is clearly not scalable.
How can I upsert a Model instance without providing a PK id in params?
class Pet extends Model { }
//...you might have the id for the pet from other sources..call it petId
const aPet = Pet.findCreateFind({where: {id: petId}});
aPet.attribute1 = 'xyz';
aPet.attribute2 = 42;
aPet.save();

Graphiql variables not being passed to server

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}) => {

Implementing pagination with Mongoose and graphql-yoga

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)
}

Graphql: How can get field arguments of a type in resolver?

I use graphql-tools library and makeExecutableSchema function to make my schema by passing schema and resolver to it
here is my schema:
type Trip {
code: String!
driver: User!
vehicle: Vehicle!
destination: Location!
passengers(page: Int, count: Int): [User!]!
}
type Query {
trip(id: String!): Trip
}
and here is my resolver:
// some imports...
export default {
Query: {
async trip(_, { id }, ctx, info) {
const trip = await Trip.findById(id);
// const page = ???, count = ???
// work on fetch data...
return result;
},
};
how can I get page and count which are defined as nested argument for passengers?
You should define a resolver for the type Trip, such as:
export default {
Query: {
async trip(_, { id }, ctx, info) {
const trip = await Trip.findById(id);
// const page = ???, count = ???
// work on fetch data...
return result;
},
Trip: {
async passengers(trip, { page, count }, ctx, info) {
...
},
}
};
In GraphQL, it's not the concept of "nested fields of a type", but just combinations of "the type of a field". The trip field of type Query has the Trip type, so when you want to work with the passengers field, it should be considered as a field under the Trip type, not a nested field of the Query type.

Resources