How to define parameters/arguments to a list resolver? - graphql-js

I have this schema and the corresponding resolvers:
const schema = buildSchema(
`
type Query {
posts(id: Int): [Post]
}
type Post {
id: Int!,
title: String,
date: String
}`
);
const resolvers = {
posts(root, { id }, context, info) {
console.log(id); // Undefined
return [
{
id: 0,
date: '21/04/2018',
title: 'Post 1'
},
{
id: 1,
date: '07/10/2018',
title: 'Post 2'
}
];
},
Post(postObj) {
return {
id: postObj.id,
title: postObj.title,
date: postObj.date
}
}
}
The problem is that when I query for posts with an specified id, like this:
query {
posts(id: 0) {
title
}
}
... I get an error that says I haven't defined such argument (id).
I defined the id argument according to the GraphQL Docs. Any suggestions of what may be causing this error and how to solve it?

When you use buildSchema, you effectively prevent yourself from being able to define custom resolvers for a given field in your schema. Instead, the default resolver will always be used. The default resolver simply takes the "parent" or "root" object for a given field, looks up the property on that parent object with the same name as the field and returns its value.
You can "get away" with this for simpler schemas by passing in a root object along with your schema. This root object then becomes the parent object referenced by the default resolver, but only for top level fields (like each field you define for your Query type). So in this case, when GraphQL resolves your query, the default resolver sees a posts property on the parent object and returns that. Because posts is actually a function, it calls the function first and then returns the value, but the arguments it calls it with are not the same arguments that a resolver is called with.
Resolvers receive four parameters -- 1) the "root" or "parent" value, 2) arguments, 3) context and 4) an "info" object containing additional data about the request. Any function called by the default resolver will only get the last 3 parameters (so no "root" value).
In other words, you should change your root object to look more like this:
const root = {
posts({ id }, context, info) {
return [
{
id: 0,
date: '21/04/2018',
title: 'Post 1'
},
{
id: 1,
date: '07/10/2018',
title: 'Post 2'
}
];
},
}
However, does it this way will only let you handle top-level fields like queries. You will not be able to customize resolver behavior for fields on other types, like Post. To do that, you should use graphql-tools' makeExecutableSchema.

Related

how to use dynamic field type in nestjs

I have a DTO structured like below, the field children I want to it to be number[] when client save the data.My database is MongoDB and I use Mongoose to manage my model.
//create-menu.dto.ts
export class CreateMenuDto {
...
#IsArray()
childrens: number[];
}
now I want it's type to be menu[] when the server response the client's query.So I define my Menu schema like below:
//menu.schema.ts
#Schema({ versionKey: false })
export class Menu {
...
#Prop({ type: Number, ref: 'Menu' })
childrens: Menu[];
}
menu.controller.ts:
#Put(':id')
update(#Param('id') id: string, #Body() updateMenuDto: UpdateMenuDto) {
return this.menuService.update(+id, updateMenuDto);
}
menu.service.ts:
update(id: number, updateMenuDto: UpdateMenuDto) {
const menu = this.menuModel.findOneAndUpdate({ _id: id }, updateMenuDto);
return menu;
}
when I use REST Client to test:
Put http://127.0.0.1:3000/api/menu/1
Content-Type: application/json
{
"childrens":[2]
}
the server throws 500 exception, so how can i implement dynamic field type? any idea is welcome, thanks!
The Menu Class has property childrens of type Menu[] while 'UpdateMenuDto' class has property childrens of type number[] so here :
const menu = this.menuModel.findOneAndUpdate({ _id: id }, updateMenuDto);
You got the error, because Nestjs cannot update the found menu document with the recieved updateMenuDto, I don't see any relation between an array of Menu and an array of number so I don't think the field childrens should exist in the UpdateMenuDto class neither in the PUT request body, but maybe you need these numbers to create a Menu array, in this case you can create a new object from updateMenuDto :
update(id: number, updateMenuDto: UpdateMenuDto) {
var newMenuDto: any = {...updateMenuDto};
newMenuDto.childrens = // use updateMenuDto.childrens to create newMenuDto.childrens of type Menu[]
const menu = this.menuModel.findOneAndUpdate({ _id: id }, newMenuDto);
return menu;
}

Prisma Typescript where clause inside include?

I am trying to query the database (Postgres) through Prisma. My query is
const products = await prisma.products.findMany({
where: { category: ProductsCategoryEnum[category] },
include: {
vehicles: {
include: {
manufacturers: { name: { in: { manufacturers.map(item => `"${item}"`) } } },
},
},
},
});
The error message is
Type '{ name: { in: { manufacturers: string; "": any; }; }; }' is not assignable to type 'boolean | manufacturersArgs'.
Object literal may only specify known properties, and 'name' does not exist in type 'manufacturersArgs'.ts(2322)
Manufacturers have the field name and it is unique; I am not sure why this is not working or how I can update this code to be able to query the database. It is like I should cast the values into Prisma arguments.
The TypeScript error is pretty self-explanatory: the name property does not exist in manufacturersArgs. The emitted Prisma Client does a great job of telling you what properties do and do not exist when filtering.
If you are trying to perform a nested filter, you need to use select instead of include.
Documentation: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#filter-a-list-of-relations
Your query is going to look something like this:
const products = await prisma.products.findMany({
where: { category: ProductsCategoryEnum[category] },
select: {
// also need to select any other fields you need here
vehicles: {
// Updated this
select: { manufacturers: true },
// Updated this to add an explicit "where" clause
where: {
manufacturers: { name: { in: { manufacturers.map(item => `"${item}"`) } } },
},
},
},
});
The final code ultimately depends on your Prisma schema. If you are using an editor like VS Code, it should provide Intellisense into the Prisma Client's TypeScript definitions. You can use that to navigate the full Prisma Client and construct your query based on exactly what is and is not available. In VS Code, hold control [Windows] or command [macOS] and click on findMany in prisma.products.findMany. This lets you browse the full Prisma Client and construct your query!
The in keyword isn't working for me. I use hasSome to find items in an array. hasEvery is also available depending what the requirements are.
hasSome: manufacturers.map(item => `"${item}"`),
See https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#scalar-list-filters

One way association with array of reference _id in sails js

I'm trying to use one way association because I need only to have reference from 1 model to other model but not vice versa.
Model Arts:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
model: 'Softwares'
}
}
}
Model Softwares:
module.exports = {
attributes: {
name: {type: 'string', required: true}
}
}
This is my api:
http://localhost:1337/api/v1/arts/create
if this is my request body, it works fine:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": "5e70309cbf12b61299d6c528",
}
but i want to store array of softwareUsed, so i tried:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": ["5e70309cbf12b61299d6c528", "5e70309cbf12b61299d6c529"],
}
but i got an error with that:
error: OperationalError [UsageError]: Invalid new record.
Details:
Could not use specified `softwareUsed`. Expecting an id representing the associated record, or `null` to indicate there will be no associated record. But the specified value is not a valid `softwareUsed`. Instead of a string (the expected pk type), the provided value is: [ '5e70309cbf12b61299d6c528', '5e70309cbf12b61299d6c529' ]
I also tried to make it array in model:
softwareUsed: [{
model: 'Softwares'
}]
but still don't work.
Is there a way to that in one way association or I need to use other association, but how can I achieve that?
Thank you.
I think you need to label the softwareUsed attribute with a collection, not a model:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
collection: 'Softwares'
}
}
}
All the documentation on one-to-many in the sails docs involves two-way associations and adding a via attribute, but I think this way works for a one-way association.
Of course, your first api call may now longer work: you may need to wrap the single software id in an array.

must have a selection of subfields. Did you mean \"createEvent { ... }\"?", [graphql] [duplicate]

Hi I am trying to learn GraphQL language. I have below snippet of code.
// Welcome to Launchpad!
// Log in to edit and save pads, run queries in GraphiQL on the right.
// Click "Download" above to get a zip with a standalone Node.js server.
// See docs and examples at https://github.com/apollographql/awesome-launchpad
// graphql-tools combines a schema string with resolvers.
import { makeExecutableSchema } from 'graphql-tools';
// Construct a schema, using GraphQL schema language
const typeDefs = `
type User {
name: String!
age: Int!
}
type Query {
me: User
}
`;
const user = { name: 'Williams', age: 26};
// Provide resolver functions for your schema fields
const resolvers = {
Query: {
me: (root, args, context) => {
return user;
},
},
};
// Required: Export the GraphQL.js schema object as "schema"
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
// Optional: Export a function to get context from the request. It accepts two
// parameters - headers (lowercased http headers) and secrets (secrets defined
// in secrets section). It must return an object (or a promise resolving to it).
export function context(headers, secrets) {
return {
headers,
secrets,
};
};
// Optional: Export a root value to be passed during execution
// export const rootValue = {};
// Optional: Export a root function, that returns root to be passed
// during execution, accepting headers and secrets. It can return a
// promise. rootFunction takes precedence over rootValue.
// export function rootFunction(headers, secrets) {
// return {
// headers,
// secrets,
// };
// };
Request:
{
me
}
Response:
{
"errors": [
{
"message": "Field \"me\" of type \"User\" must have a selection of subfields. Did you mean \"me { ... }\"?",
"locations": [
{
"line": 4,
"column": 3
}
]
}
]
}
Does anyone know what I am doing wrong ? How to fix it ?
From the docs:
A GraphQL object type has a name and fields, but at some point those
fields have to resolve to some concrete data. That's where the scalar
types come in: they represent the leaves of the query.
GraphQL requires that you construct your queries in a way that only returns concrete data. Each field has to ultimately resolve to one or more scalars (or enums). That means you cannot just request a field that resolves to a type without also indicating which fields of that type you want to get back.
That's what the error message you received is telling you -- you requested a User type, but you didn't tell GraphQL at least one field to get back from that type.
To fix it, just change your request to include name like this:
{
me {
name
}
}
... or age. Or both. You cannot, however, request a specific type and expect GraphQL to provide all the fields for it -- you will always have to provide a selection (one or more) of fields for that type.

Updating an array field in a mongodb collection

I am trying to update my collection which has an array field(initially blank) and for this I am trying this code
Industry.update({_id:industryId},
{$push:{categories: id:categoryId,
label:newCategory,
value:newCategory }}}});
No error is shown, but in my collection just empty documents({}) are created.
Note: I have both categoryId and newCategory, so no issues with that.
Thanks in advance.
This is the schema:
Industry = new Meteor.Collection("industry");
Industry.attachSchema(new SimpleSchema({
label:{
type:String
},
value:{
type:String
},
categories:{
type: [Object]
}
}));
I am not sure but maybe the error is occuring because you are not validating 'categories' in your schema. Try adding a 'blackbox:true' to your 'categories' so that it accepts any types of objects.
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [Object],
blackbox:true // allows all objects
}
}));
Once you've done that try adding values to it like this
var newObject = {
id: categoryId,
label: newCategory,
value: newCategory
}
Industry.update({
_id: industryId
}, {
$push: {
categories: newObject //newObject can be anything
}
});
This would allow you to add any kind of object into the categories field.
But you mentioned in a comment that categories is also another collection.
If you already have a SimpleSchema for categories then you could validate the categories field to only accept objects that match with the SimpleSchema for categories like this
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [categoriesSchema] // replace categoriesSchema by name of SimpleSchema for categories
}
}));
In this case only objects that match categoriesSchema will be allowed into categories field. Any other type would be filtered out. Also you wouldnt get any error on console for trying to insert other types.(which is what i think is happening when you try to insert now as no validation is specified)
EDIT : EXPLANATION OF ANSWER
In a SimpleSchema when you define an array of objects you have to validate it,ie, you have to tell it what objects it can accept and what it can't.
For example when you define it like
...
categories: {
type: [categoriesSchema] // Correct
}
it means that objects that are similar in structure to those in another SimpleSchema named categoriesSchema only can be inserted into it. According to your example any object you try to insert should be of this format
{
id: categoryId,
label: newCategory,
value: newCategory
}
Any object that isn't of this format will be rejected while insert. Thats why all objects you tried to insert where rejected when you tried initially with your schema structured like this
...
categories: {
type: [Object] // Not correct as there is no SimpleSchema named 'Object' to match with
}
Blackbox:true
Now, lets say you don't what your object to be filtered and want all objects to be inserted without validation.
Thats where setting "blackbox:true" comes in. If you define a field like this
...
categories: {
type: [Object], // Correct
blackbox:true
}
it means that categories can be any object and need not be validated with respect to some other SimpleSchema. So whatever you try to insert gets accepted.
If you run this query in mongo shell, it will produce a log like matched:1, updated:0. Please check what you will get . if matched is 0, it means that your input query is not having any matching documents.