GraphQL - show list or single object - node.js

I have managed to get following data through GraphQL:
{
"data": {
"city": {
"name": "Eldorado",
"users": [
{
"username": "lgraham1"
},
{
"username": "ehowell"
},
{
"username": "cbauch"
}
]
}
}
}
I have QueryType, CityType and UserType. In my QueryType I fetch city and display users by GraphQLList(UserType). What should I do if I want to display single user if there is an id provided?
My API looks like this:
all cities:
/cities/
single city:
/cities/:city_id
users for particular city:
/cities/:city_id/users
single user:
/cities/:city_id/users/:user_id

You'll need to add a user query to your main Query object.
Assuming your id is an Integer, you would do this
const Query = new GraphQLObjectType({
name: 'RootQuery',
fields: {
// ...
user: {
type: User,
args: {
id: {
type: new GraphQLNonNull(GraphQLInt)
}
},
resolve: function(rootValue, args) {
return db.users.findOne(args)
}
}
}
})
const Schema = new GraphQLSchema({
query: Query,
// ...
});
Then you can query using
{
user (id: 12345) {
...
}
}
Or you could make a function
query findUser ($id: Int!) {
user (id: $id) {
...
}
}

Related

Node.js - How to get associated model data using graphql?

In Node.js app, I am using graphql to get the list of data. I have created two models called School and Grade. Association for these models like School has many Grades and Grade belongs to School.
While querying I am getting null value for associated model.
In model school.js,
module.exports = (sequelize, Sequelize) => {
const School = sequelize.define("school", {
name: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
},
});
School.associate = function (models) {
School.hasMany(models.Grade, { foreignKey: "school_id" });
};
return School;
};
In typeDefs.graphql,
type Query {
getSchoolDetails: [School]
getSchoolDetail(id: ID!): School
getGradeDetails: [Grade]
}
type School {
id: ID!
name: String
email: String
grades: [Grade]
}
type Grade {
id: ID!
school_id: ID!
name: String
school: School
}
In resolvers.js,
const Query = {
getSchoolDetails: async () => {
try {
const schools = await school.findAll();
return schools;
} catch (err) {
console.log(err);
}
},
getSchoolDetail: async (root, { id }) => {
try {
const scl = await school.findByPk(id);
return scl;
} catch (err) {
console.log(err);
}
},
getGradeDetails: async () => {
try {
const grades = await grade.findAll({});
return grades;
} catch (err) {
console.log(err);
}
},
}
In playground when I query like,
query {
getSchoolDetails{
id
name
email
grades{
name
}
}
}
Output for this is,
{
"data": {
"getSchoolDetails": [
{
"id": "1",
"name": "Rotary West School",
"email": "rotary#gmail.com",
"grades": null
},
{
"id": "2",
"name": "Excel Public School",
"email": "excel#gmail.com",
"grades": null
},
]
}
Same way when I query to get Grades will get school as null. I am learning nodejs relations with graphql, please help me to solve this.
You have to use include with your queries in resolvers.js file like below
getSchoolDetails: async () => {
try {
const schools = await school.findAll({
include: [{ model: Grade }],
});
return schools;
} catch (err) {
console.log(err);
}
},
This Will get all the grades associated with schools
And if you want to include school with grade you have to associate grade with school using Grades belongsTo school

Get all data from subdocument via graphql

Model
const mongoose=require('mongoose');
const CustomerGeneralInformation = require('./CustomerGeneralInformation').schema;
const CustomerContact = require('./CustomerContact').schema;
const CustomerFinance=require('./CustomerFinancial').schema;
const CustomerPersonalData=require('./CustomerPersonalData').schema;
const CustomerIdentification=require('./CustomerIdentification').schema;
const Schema=mongoose.Schema;
const CustomerSchema=new Schema({
generalInformation: [CustomerGeneralInformation],
contactData: [CustomerContact],
financialData:[CustomerFinance],
personalData:[CustomerPersonalData],
identificationData:[CustomerIdentification]
});
module.exports=mongoose.model('Customer',CustomerSchema);
Graphql Schema
const RootQuery=new GraphQLObjectType({
name:'RootQueryType',
fields:{
customers:{
type: new GraphQLList(CustomerType),
resolve(parent,args){
return customer.find({});
}
}
}
});
GraphiQl
{
customers
{
id
generalInformation{
purposeOfBusiness
}
contactData{
phone
email
countryResidence
mailbox
houseNo
zip
city
}
financialData{
taxNo
countryTaxation
refBankIban
StringrefBankBic
refBankIban
}
}
}
Result
{
"data": {
"customers": [
{
"id": "5fa0f8ea4e028a2cf8d24c3f",
"generalInformation": {
"purposeOfBusiness": null
},
"contactData": {
"phone": null,
"email": null,
"countryResidence": null,
"mailbox": null,
"houseNo": null,
"zip": null,
"city": null
},
"financialData": {
"taxNo": null,
"countryTaxation": null,
"refBankIban": null,
"StringrefBankBic": null
}
}
]
}
}
SO I have created a customer model which have an id(autogenerated by mongodb) and 5 other fields of schema type.
I have used graphql for creating apis.
Everything is working fine, I have created mutations to add data, its all tested in mongo that data is being saved.
But if you check my graphql output I am unable to query subdocuments, it shows null. I want list of all subdocuments with data.
PlEASE HELP
I had tried this way and its working perfectly,
Firstly as you are passing the whole model in your new Customer model so instead of that I just have stored the ID as a reference.
const CustomerSchema = new mongoose.Schema(
{
generalInformation: {
type: mongoose.Schema.Types.ObjectId,
ref: "GeneralInfo"
},
contactData: { type: mongoose.Schema.Types.ObjectId, ref: "ContactData" },
financialData: {
type: mongoose.Schema.Types.ObjectId,
ref: "FinacialData"
},
personalData: { type: mongoose.Schema.Types.ObjectId, ref: "PersonalData" }
},
{
timestamps: true
}
);
After that, for CustomerType, I had done this
const CustomerType = new GraphQLObjectType({
name: "Customer",
fields: () => ({
id: { type: GraphQLID },
customerContact: {
type: CustomerContactType,
resolve(parent, args) {
return CustomerContact.findById(parent.contactData);
}
},
generalInformation: {
type: CustomerGeneralType,
resolve(parent, args) {
return CustomerGeneral.findById(parent.generalInformation);
}
},
financialData: {
type: CustomerFinanceType,
resolve(parent, args) {
return CustomerFinance.findById(parent.financialData);
}
},
personalData: {
type: CustomerPersonalType,
resolve(parent, args) {
return CustomerPersonal.findById(parent.personalData);
}
}
})
});
And them for RootQuery
customers: {
type: new GraphQLList(CustomerType),
resolve(parent, args) {
return Customar.find({});
}
}
and here is my Query:
{
customers{
id
generalInformation{
purposeOfBusiness
}
customerContact{
phone
email
countryResidence
}
financialData{
taxNo
}
}
}
And Output
{
"data": {
"customers": [
{
"id": "5fa3fe066f0fda0568e56456",
"generalInformation": {
"purposeOfBusiness": "Test Purpose"
},
"customerContact": {
"phone": "123456798",
"email": "test#demo.com",
"countryResidence": "Pak"
},
"financialData": {
"taxNo": "120033244"
}
}
]
}
}

How to save dynamic number of variables into database using graphql?

I'm trying to mutate and query dynamic variables. The user has the choice to add as many variables as they want before sending them off to the server. For example, my app is a productivity app that allows a user to add as many metrics as they want to track their goal so if "Gym" is their goal, the metrics would be "running", "bench press", etc. My problem is, I'm unsure how to save them in the database since there is no pre-configured Schema for these user-created variables.
I've managed to send the variables to the back end using the following:
mutation CreateGoal ($title: String!, $description: String, $metric: [Json!]) {
createGoal(
data: {
title: $title
description: $description
metric: { set: $metric }
}
){
id
}
}
Schema:
type Mutation {
createGoal(data: CreateGoalInput!): Goal!
}
input CreateGoalInput {
title: String!
description: String
metric: GoalCreatemetricInput
}
input GoalCreatemetricInput {
set: [Json!]
}
Once the variables arrive in the resolver, it's in the Json format:
{ set: [ 'running', 'bench press' ] }
Normally, I'd simply save the variables through Prisma:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
}
}, info)
},
However, since the number of variables are unknown, how do I save 'metric' into my database?
If I were to try the following:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric,
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value [ "running", "bench
press" ] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput.
If I were to try:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: metric.set
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value ["running", "bench
press"] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput. Variable "$_v0_data" got invalid value
["Asdfasdf", "Asdfasdf"] at "_v0_data.metric"; Field "1" is not
defined by type GoalCreatemetricInput.
I don't think you need to use the Json scalar at all. It looks like you're trying to pass an array of strings so instead of [Json!] you may just need to use [String!].
input CreateGoalInput {
title: String!
description: String
metric: [String!]
}
Then you should be able to get rid of
input GoalCreatemetricInput {
set: [Json!]
}
Here you should be able to pass the array of strings to the backend:
mutation CreateGoal ($title: String!, $description: String, $metric: [String!]) {
createGoal(
data: {
title: $title
description: $description
metric: $metric
}
){
id
}
}
And in your resolover I think all you need to do is:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: { set: metric },
}
}, info)
},

How to update mongodb with multiple dynamic keys?

Server receives three dynamic keys: map_id -> section_id -> subsection_id.
User model:
{
_id: userId,
skills: {
[map_id]: {
[section_id]: {
[subsection_id]: {
...subsectionData
}
}
},
...otherMapKeys
}
}
How to update subsection data?
This solution with mongoose Model.update works correctly:
const dynamicLink = `skills.${map_id}.${section_id}.${subsection_id}`;
userModel.update(
{ id: userId },
{ $set: { [dynamicLink]: subsection } },
{ upsert: true }
);

Unable to fetch list in react relay

I am following schema same as mentioned here
I want to fetch all users so I updated my schema like this
var Root = new GraphQLObjectType({
name: 'Root',
fields: () => ({
user: {
type: userType,
resolve: (rootValue, _) => {
return getUser(rootValue)
}
},
post: {
type: postType,
args: {
...connectionArgs,
postID: {type: GraphQLString}
},
resolve: (rootValue, args) => {
return getPost(args.postID).then(function(data){
return data[0];
}).then(null,function(err){
return err;
});
}
},
users:{
type: new GraphQLList(userType),
resolve: (root) =>getUsers(),
},
})
});
And in database.js
export function getUsers(params) {
console.log("getUsers",params)
return new Promise((resolve, reject) => {
User.find({}).exec({}, function(err, users) {
if (err) {
resolve({})
} else {
resolve(users)
}
});
})
}
I am getting results in /graphql as
{
users {
id,
fullName
}
}
and results as
{
"data": {
"users": [
{
"id": "VXNlcjo1Nzk4NWQxNmIwYWYxYWY2MTc3MGJlNTA=",
"fullName": "Akshay"
},
{
"id": "VXNlcjo1Nzk4YTRkNTBjMWJlZTg1MzFmN2IzMzI=",
"fullName": "jitendra"
},
{
"id": "VXNlcjo1NzliNjcyMmRlNjRlZTI2MTFkMWEyMTk=",
"fullName": "akshay1"
},
{
"id": "VXNlcjo1NzliNjgwMDc4YTYwMTZjMTM0ZmMxZWM=",
"fullName": "Akshay2"
},
{
"id": "VXNlcjo1NzlmMTNkYjMzNTNkODQ0MmJjOWQzZDU=",
"fullName": "test"
}
]
}
}
but If I try to fetch this in view as
export default Relay.createContainer(UserList, {
fragments: {
userslist: () => Relay.QL`
fragment on User #relay(plural: true) {
fullName,
local{
email
},
images{
full
},
currentPostCount,
isPremium,
}
`,
},
});
I am getting error Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.
Please tell me what I am missing .
I tried a lot with and without #relay(plural: true).
Also tried to update schema with arguments as
users:{
type: new GraphQLList(userType),
args: {
names: {
type: GraphQLString,
},
...connectionArgs,
},
resolve: (root, {names}) =>connectionFromArray(getUsers(names)),
},
but I got error Cannot read property 'after' of undefined in implementing react-relay
Thanks in Advance.
Relay currently only supports three types of root fields (see facebook/relay#112):
Root field without arguments, returning a single node:
e.g. { user { id } } returning {"id": "123"}
Root field with one argument, returning a single node:
e.g. { post(id: "456") { id } } returning {"id": "456"}
Root field with one array argument returning an array of nodes with the same size as the argument array (also known as "a plural identifying root field"):
e.g. { users(ids: ["123", "321"]) { id } } returning [{"id": "123"}, {"id": "321"}]
A workaround is to create a root field (often called viewer) returning a node that has those fields. When nested inside the Viewer (or any other node), fields are allowed to have any return type, including a list or connection. When you've wrapped the fields in this object in your GraphQL server, you can query them like this:
{
viewer {
users {
id,
fullName,
}
}
}
The Viewer type is a node type, and since there will just be one instance of it, its id should be a constant. You can use the globalIdField helper to define the id field, and add any other fields you want to query with Relay:
const viewerType = new GraphQLObjectType({
name: 'Viewer',
interfaces: [nodeInterface],
fields: {
id: globalIdField('Viewer', () => 'VIEWER_ID'),
users:{
type: new GraphQLList(userType),
resolve: (viewer) => getUsers(),
},
},
});
On the client you'll need to change the root query in your route to { viewer } and define the fragment on Viewer:
export default Relay.createContainer(UserList, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
users {
fullName,
local {
email,
},
images {
full,
},
currentPostCount,
isPremium,
}
}
`,
},
});

Resources