[Previously titled "How to get 1 record from a list..."]
I am very new to GraphQL and trying to understand how to get 1 record from query.
This is the result of my current query:
{
"data": {
"todos": null
}
}
I am not sure what is wrong. I would like the result to be this:
{
"data": {
"todos": {
"id": 1,
"title": "wake up",
"completed": true
}
}
}
Here is my code that I've created as I try to learn GraphQL.
schema.js:
var graphql = require('graphql');
var TODOs = [
{
"id": 1,
"title": "wake up",
"completed": true
},
{
"id": 2,
"title": "Eat Breakfast",
"completed": true
},
{
"id": 3,
"title": "Go to school",
"completed": false
}
];
var TodoType = new graphql.GraphQLObjectType({
name: 'todo',
fields: function () {
return {
id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
};
}
});
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
todos: {
type: new graphql.GraphQLList(TodoType),
args: {
id: { type: graphql.GraphQLID }
},
resolve: function (source, args, root, ast) {
if (args.id) {
return TODOs.filter(function(item) {
return item.id === args.id;
})[0];
}
return TODOs;
}
}
}
}
});
module.exports = new graphql.GraphQLSchema({
query: queryType
});
index.js:
var graphql = require ('graphql').graphql;
var express = require('express');
var graphQLHTTP = require('express-graphql');
var Schema = require('./schema');
var query = 'query { todos(id: 1) { id, title, completed } }';
graphql(Schema, query).then( function(result) {
console.log(JSON.stringify(result,null," "));
});
var app = express()
.use('/', graphQLHTTP({ schema: Schema, pretty: true }))
.listen(8080, function (err) {
console.log('GraphQL Server is now running on localhost:8080');
});
To run this code I just run node index from the root directory. How can I get one specific record returned by the records id?
You have the wrong type for the todos field of your queryType. It should be TodoType, not a list of TodoType. You're getting an error because GraphQL expects to see a list, but your resolver is just returning a single value.
By the way, I suggest passing the graphiql: true option to graphqlHTTP, which will let you use GraphiQL to explore your schema and make queries.
Related
So I have been tasked with making a basic workflow engine. I have it checking the config file for dependencies and then performing the next function. what I would like to is check to see if the element already exists and not push it to the output array. I have tried .includes() and .indexOf() but I cant seem to get them to work.
const TestWorkflowConfig = {
insertDetails: {
dependencies: [],
workflow: { name: "updateRow", id: 10 },
description: "This is a test function where details need to be entered (row update)",
data: {},
taskName: "insertDetails",
},
detailsConfirmed: {
{ insertDetails: { isRequired: true; } }
dependencies: ["insertDetails"],
workflow: { name: "updateRow", id: 10; },
description: "this is to confirm details (update row status)",
data: {},
taskName: "detailsConfirmed",
},
sendConfirmationEmail: {
dependencies: ["detailsConfirmed"],
workflow: { name: "sendEmail", id: 8; },
description: "this is a test email to send out to confirm details (send email workflow)",
data: { name: "james", email: "james#email.com", body: "this is a test email please ignore"; },
taskName: "sendConfirmationEmail",
},
};
const taskQueue = [
{
"processName": "sendConfirmationEmail",
"isComplete": false
},
{
"processName": "detailsConfirmed",
"isComplete": false
},
{
"processName": "insertDetails",
"isComplete": true
}
];
const workflowTaskQueue = [];
const config = TestWorkflowConfig;
const completedJobs = [];
for (const element of taskQueue) {
if (element.isComplete === true) {
completedJobs.push(element.processName);
}
}
for (const element in config) {
if (config[element].dependencies <= 0) {
// I would like to check if the "config[element]" is in the completedJobs array and only push if it is not there.
workflowTaskQueue.push({ json: config[element] });
} else {
const dependencies = config[element].dependencies;
const dep = [];
for (const el of dependencies) {
dep.push(el);
const result = dep.every((i) => completedJobs.includes(i));
if (result === true) {
// and again I would like to check if the "config[element]" is in the completedJobs array and only push if it is not there
workflowTaskQueue.push({ json: config[element] });
}
}
console.log("taskQueue: " + taskQueue);
console.log("completedJobs: " + completedJobs);
console.log("output:" + workflowTaskQueue);
}
}
as always any help is greatly appreciated.
I'm trying to create a quiz application with node.js as a backend language. I am trying to get all the questions and for every question in the array another "answers" array with every answer for that question. I want to get this information as a get request.
pic of my database tables
the format of the output I want would look like this:
[
{
"questionText": "Question1How much do you like Python?",
"answers": [
{
"answerText": "Q1RESPONSE OPTION 1",
"isCorrect": false,
"score": 0
},
{
"answerText": "Q1RESPONSE OPTION 2",
"isCorrect": true,
"score": 10
}
]
},
{
"questionText": "Question2How much do you like Python?",
"answers": [
{
"answerText": "Q2RESPONSE OPTION 1",
"isCorrect": false,
"score": 0
},
{
"answerText": "Q2RESPONSE OPTION 2",
"isCorrect": true,
"score": 10
}
]
}
]
So far I have made this controller function for a get request
const getAllAnswersForQuestion = async (req, res) => {
try {
let question_id = req.params.question_id;
const question = await Question.findOne({ where: { id: question_id } });
const answers = await Answer.findAll({
where: {
questionId: question_id
}
})
if (answers.length > 0) {
res.status(200).send({ question, answers });
} else
res.status(404).json({ message: "question has 0 answers" });
} catch (e) {
console.error(e);
res.status(500).send({ message: "server error" });
}
};
But the output I receive looks like so:
{
"question": {
"id": 1,
"questionText": "Q1How much do you like Python?",
"testId": 1
},
"answers": [
{
"id": 1,
"answerText": "Q1RESPONSE OPTION 1",
"isCorrect": false,
"score": 0,
"questionId": 1
},
{
"id": 2,
"answerText": "Q1RESPONSE OPTION 2",
"isCorrect": true,
"score": 10,
"questionId": 1
}
]
}
Do you have any idea of how I could create a function to append to the output object an array of subcomponents, like for every question to return the answers inside its object, and then return all the questions like specified above?
Thank you in advance!
So, you should have two schemas to be relationated, and on looking up for Questions just populate each answer.
Example:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const questionSchema = Schema({
_id: Schema.Types.ObjectId,
questionText: String,
/// ...
answers: [{ type: Schema.Types.ObjectId, ref: 'Answer' }]
});
const Question = mongoose.model('Question ', questionSchema );
const answerSchema = Schema({
answerText: String,
isCorrect: Boolean,
/// ...
});
const Answer = mongoose.model('Answer', answerSchema );
Looking for questions with it all answers:
const getAllAnswersForQuestion = async (req, res) => {
try {
let question_id = req.params.question_id;
const question = await Question.findOne({ where: { id: question_id } }).populate("answers") // populate the property you want, in this case "asnwers"
if (question .length > 0) {
res.status(200).send({ question});
} else
res.status(404).json({ message: "question has 0 answers" });
} catch (e) {
console.error(e);
res.status(500).send({ message: "server error" });
}
};
So, I have managed to resolve it eventually by creating another function, I don't know if this will help somebody ever but I will leave it here. The raw: true part is very important for getting only the data in the table, without the additional things added to the object in the browser.
const getAllQuestions = async (req, res) => {
try {
let test_id = req.params.test_id;
const questions = await Question.findAll({raw:true, where:{
testId:test_id
}}).then(async (questionsFound) => {
for (let i = 0; i < questionsFound.length; i++) {
questionsFound[i].answers = await getAllAnswersQuestion(questionsFound[i].id)
}
return questionsFound;
});
res.status(200).send(questions);
} catch (e) {
console.error(e);
res.status(500).send({ message: "server error" });
}
}
const getAllAnswersQuestion = async (id) => {
const answers = await Answer.findAll({
where: {
questionId: id
},
raw: true
})
return answers
};
I didn't want to use mongoose because it is something new for me.
I'd like to normalize data in this format:
[
{
id: 3,
status: "active",
created: "2017-07-03T15:36:11+02:00",
published: "2017-07-03T15:38:07+02:00",
linkables: [
{
data: {
content: "Text content of the linkable",
id: 200
}
linkable_type: "content",
module_id: 3,
module_type: "case_study"
},
{
data: {
content: "https://foobar.wee/url_of_the_media.png",
id: 205
}
linkable_type: "media",
module_id: 3,
module_type: "case_study"
},
]
},
...
]
into something like this:
{
entities: {
contents: {
200: {
content: "Text content of the linkable",
id: 200
}
},
media: {
205: {
content: "https://foobar.wee/url_of_the_media.png",
id: 205
}
},
linkables: {
3_case_study_content_200: {
data: 200, // in the contents key
linkable_type: "content",
module_id: 3,
module_type: "case_study"
},
3_case_study_media_205: {
data: 205, // in the media key
linkable_type: "media",
module_id: 3,
module_type: "case_study"
}
},
caseStudies: {
3: {
id: 3,
status: "active",
created: "2017-07-03T15:36:11+02:00",
linkables: ["3_case_study_content_200", "3_case_study_media_205"]
}
}
},
result: [3]
}
Using the 2.x version of the normalizr makes this easy thanks to this PR https://github.com/paularmstrong/normalizr/pull/132 that adds a function one can use to dynamically decide what scheme to use when normalizing by the value in the parent object.
However, in the version 3.x.x this additional function did not make it. According to the docs, I'd say that schemaAttribute does the job.
Having this piece of code unfortunately does not give me the wanted result...
export const caseStudies = new schema.Entity('caseStudies');
export const media = new schema.Entity('media',);
export const content = new schema.Entity('contents');
export const linkablesSchema = new schema.Entity(
'linkables',
{},
{
idAttribute: (itm) => {
const id = itm.data.id;
return `${itm.module_id}_${itm.module_type}_${itm.linkable_type}_${id}`;
}
}
);
export const linkableSchemaMap = {
content,
media
};
caseStudies.define({
linkables: [linkablesSchema]
});
export const lschema = new schema.Array(linkableSchemaMap, (value, parent) => {
return parent.linkable_type; // this would replace the PR mentioned above?
});
linkablesSchema.define({
data: lschema
});
Thank you for your help!
The most straightforward way that came to mind is to use processStrategy inside of the linkablesSchema options to apply linkable_type value as a property:
export const caseStudies = new schema.Entity("caseStudies");
export const mediaSchema = new schema.Entity("media");
export const contentsSchema = new schema.Entity("contents");
export const linkablesSchema = new schema.Entity("linkables", {}, {
idAttribute: itm => `${itm.module_id}_${itm.module_type}_${itm.linkable_type}_${itm.data.id}`,
processStrategy: (value) => ({
...value,
data: value.data.id,
[value.linkable_type]: value.data,
})
});
linkablesSchema.define({
content: contentsSchema,
media: mediaSchema
})
caseStudies.define({
linkables: [linkablesSchema]
});
normalize(data, [caseStudies]);
And here's the sandbox. Hope it helps.
I'm developing an app using Loopback 3. I've created a remote method on on the ServiceEvaluation Model to return a list of ServiceEvaluations and related properties in the Service Model.
ServiceEvaluation.evaluationList = function(cb) {
ServiceEvaluation.find({
fields: {
status: true,
createdAt: true,
serviceId: true
},
include: {
relation: 'rel_ServiceEval_Service',
scope: {
fields: {
serviceName: true,
supplierId: true
}
}
}
}, function(err, response) {
cb(null, response);
});
};
The above works when called from the API Explorer returning;
{
"list": [
{
"status": "Draft",
"serviceId": "5b8e215d81c76325b409b960",
"createdAt": "2018-09-04T06:08:29.623Z",
"rel_ServiceEval_Service": {
"serviceName": "Workplace software and SaaS",
"id": "5b8e215d81c76325b409b960",
"supplierId": "5b8e215d81c76325b409b949"
}
}, ...
However, rather than returning an array of objects with embedded objects, I want to return a array of flattened objects to display in a data grid. The below is an attempt to do so.
ServiceEvaluation.evaluationList = function(cb) {
ServiceEvaluation.find({
fields: {
status: true,
createdAt: true,
serviceId: true
},
include: {
relation: 'rel_ServiceEval_Service',
scope: {
fields: {
serviceName: true,
supplierId: true
}
}
}
}, function(err, response) {
var responseLength = response.length;
var myEntry = {};
var myList = [];
for (var i = 0; i < responseLength; i++) {
myEntry.status = response[i].status;
myEntry.createdAt = response[i].createdAt;
myEntry.serviceName = response[i].rel_ServiceEval_Service.serviceName;
myEntry.supplierId = response[i].rel_ServiceEval_Service.supplierId;
myList.push(myEntry);
}
cb(null, myList);
});
};
The result of this is that the remote method seems not to find the fields in the rel_ServiceEval_Service.
{
"list": [
{
"status": "Draft",
"createdAt": "2018-09-04T06:20:40.889Z"
}, ...
I've resorted to flattening the return value in a service on the client side but this isn only a temp solution while in Dev. Any guidance on how to do this in the remote method?
you need to use .toJSON() to serialize the returned data:
ServiceEvaluation.evaluationList = function(cb) {
ServiceEvaluation.find({
fields: {
status: true,
createdAt: true,
serviceId: true
},
include: {
relation: 'rel_ServiceEval_Service',
scope: {
fields: {
serviceName: true,
supplierId: true
}
}
}
}, function(err, response) {
var myEntry = {};
var myList = [];
async.map(response, function(singleItem,callback){
serializedSingleItem = singleItem.toJSON()
var myEntry = {status: serializedSingleItem.status, createdAt: serializedSingleItem.createdAt, serviceName: serializedSingleItem["rel_ServiceEval_Service"]["serviceName"], supplierId: serializedSingleItem["rel_ServiceEval_Service"]["supplierId"]}
callback(null, myEntry)
}, function(err, myList){
cb(null, myList)
})
});
};
I need to change my schema
const dataSchema = new Schema({
trackingPrice: [{
timeCheck: { type: Date, default: Date.now },
lowestPrice: Number,
salePrice: Number,
saleRank: Number
}]
})
to this
const dataSchema = new Schema({
trackingPrice: [{
timeCheck: { type: Date, default: Date.now },
lowestPrice: Number,
salePrice: Number
}],
trackingRank: [{
timeCheck: { type: Date, default: Date.now },
saleRank: Number
}]
})
How can I transfer my data from one to another under document and then delete "saleRank"?
Based on this very good answer, the cursor in your case would be derived from running the aggregate pipeline:
const pipeline = [
{
"$project": {
"trackingPrice": {
"$map": {
"input": "$trackingPrice",
"as": "el",
"in": {
"timeCheck": "$$el.timeCheck",
"lowestPrice": "$$el.timeCheck",
"salePrice": "$$el.salePrice"
}
}
},
"trackingRank": {
"$map": {
"input": "$trackingPrice",
"as": "el",
"in": {
"timeCheck": "$$el.timeCheck",
"saleRank": "$$el.saleRank"
}
}
}
}
}
];
const cursor = Data.aggregate(pipeline).exec();
Running the bulk update:
let bulkUpdateOps = [];
cursor.then(results => {
results.forEach(doc => {
const { _id, trackingPrice, trackingRank } = doc;
bulkUpdateOps.push({
"updateOne": {
"filter": { _id },
"update": { "$set": { trackingPrice, trackingRank } },
"upsert": true
}
});
});
if (bulkUpdateOps.length === 1000) {
bulkUpdateOps = [];
return Data.bulkWrite(bulkUpdateOps);
}
}).then(console.log).catch(console.error);
if (bulkUpdateOps.length > 0) {
Data.bulkWrite(bulkUpdateOps).then(console.log).catch(console.error);
}
The best way I see is to find all of them, and foreach one create a new one
I'm using mongoose by the way
dataSchema.find({}, (err,all) => {
var array = [];
all.foreach( (ds) => {
array.push({
trackingPrice: ds.trackingPrice,
trackingRank: //whatever way you want to update the old data
})
dataSchema.remove({}).exec(() => {
array.foreach((a) => {
var toadd = new dataSchema({
trackingPrice:a.trackingPrice,
trackingRank:a.trackingRank});
toadd.save();
})
})
})}
);