Hello I'm not new to Flutter but super new to Graphql. I have this Resolver in NestJs
#Mutation(() => Recipe)
createRecipe(
#Args('createRecipeInput') createRecipeInput: CreateRecipeInput,
) {
return this.recipesService.create(createRecipeInput);
}
The DTO looks like this.
#InputType()
export class CreateRecipeInput {
#Field(() => [String], { nullable: true })
ingredientNames?: string[];
#Field(() => [String], { nullable: true })
instructionNames?: string[];
}
My mutation
const String createRecipe = r'''
mutation CreateRecipe(
$ingredientNames: [String!]!,
$instructionNames: [String!]!,
) {
action: createRecipe(createRecipeInput: {
instructionNames: $instructionNames,
}) {
ingredientNames
instructionNames
}
}
''';
final MutationOptions options = MutationOptions(
document: gql(createRecipe),
operationName: 'CreateRecipe',
variables: <String, dynamic>{
'ingredientNames': ingredientNames,
'instructionNames': instructionNames,
},
);
await client.mutate(options);
I get this error
{"errors":[{"message":"Cannot query field \"ingredientNames\" on type \"Recipe\". Did you mean \"ingredients\"?","locations":[{"line":8,"column":5}],"extensions":
Is like the request is not mapping to the DTO but the actual entity. I tried this example using the playground and postman and in both this code works. Not sure if the library is busted or I'm just missing something.
Thanks.
Related
I am coding a CRUD API built in TypeScript and TypeGoose.
I get an error saying,
CannotDetermineGraphQLTypeError: Cannot determine GraphQL output type for '_id' of 'User' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper output value?
I have a User entity.
import { Field, ObjectType } from 'type-graphql';
import { ObjectId } from 'mongodb';
import { prop as Property, getModelForClass } from '#typegoose/typegoose';
#ObjectType()
export class User {
#Field()
readonly _id: ObjectId;
#Field()
#Property({ required: true })
email: string;
#Field({ nullable: true })
#Property()
nickname?: string;
#Property({ required: true })
password: string;
constructor(email: string, password: string) {
this.email = email;
this.password = password;
}
}
export const UserModel = getModelForClass(User);
And this is how my query resolver looks like.
#Query(() => [User])
async users() {
const users = await UserModel.find();
console.log(users);
return users;
}
How can I solve this? It seems to be like TypeGraphQL doesn't understand what the MongoDB ID is?
Im not sure about this, but maybe ObjectId.toString() help you.
MongoDB doc about ObjectId.toString()
I have the following API route in Next:
import {NextApiRequest, NextApiResponse} from "next";
import dbConnect from "../../utils/dbConnect";
import {UserModel} from "../../models/user";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "GET") return res.status(405);
if (!req.query.id || Array.isArray(req.query.id)) return res.status(406).json({message: "No ID found in request"});
try {
await dbConnect();
const user = await UserModel.findOne({ _id: req.query.id });
if (!user) return res.status(404).json({message: "No user found"});
return res.status(200).json({data: user});
} catch (e) {
return res.status(500).json({message: e});
}
}
Typescript says that the line const user = await UserModel.findOne({ _id: req.query.id }); contains error Type 'string' is not assignable to type 'Condition<UserObj>'. Creating an ObjectId instead of a string (const user = await UserModel.findOne({ _id: mongoose.Types.ObjectId(req.query.id) });) throws the same error.
I've looked through the type files/docs but I'm struggling to figure out why this is invalid. Shouldn't querying by ID with a string or ObjectId be a valid condition object? Querying by other fields works fine.
Why is this invalid, and how should I fix it?
The proposed solution by #Tim is good and solves this punctual situation, but it doesn't get you to the root of the problem. What if you have to use the findOne method because you are going to use another field in the filter? For example:
You want to get the user with that id and that the deletedAt attribute is null.
const user = await UserModel.findOne({ _id: req.query.id, deletedAt: null});
You will get the same error cause the mistake is in the userModel definition. I guess your user class is basically as shown below:
import { ObjectId, Types } from 'mongoose';
#Schema({ versionKey: false, timestamps: true })
export class User {
#Field(() => ID, {name: 'id'})
readonly _id: ObjectId;
#Field(() => Date, {nullable: true, name: 'deleted_at'})
#Prop({type: Date, required: false, default: null})
deletedAt?: Date;
#Field()
#Prop({required: true, index: true})
name: string;
...
}
The problem is that you are directly accessing the Schema user when you should be accessing the model (repository pattern).
[SOLUTION]: Create the model or the repository for your user class, and use it to interact with your database.
In my case I just added the following lines:
import { ObjectId, Types, Document } from 'mongoose';
#Schema({ versionKey: false, timestamps: true })
export class User {
...
}
export type UserDocument = User & Document;
OR
import { ObjectId, Types, Document } from 'mongoose';
#Schema({ versionKey: false, timestamps: true })
export class User extends Document{
...
}
and in my service I instantiated an object of type model:
import { Model } from 'mongoose';
private userModel: Model<UserDocument>;
and then I was able to make the following method call:
...
await dbConnect();
const user = await UserModel.findOne({ _id: req.query.id });
if (!user) return res.status(404).json({message: "No user found"});
...
Use .findByID for id based queries.
I am trying to insert a validation into PUT request(to update some data stored in MongoDB):
DTO:
export enum reportFields {
'startDate',
'targetDateOfCompletion',
'duration',
}
export class updateScheduleDto {
#IsOptional()
#IsString()
readonly frequency?: string;
#IsOptional()
#IsArray()
#IsEmail({}, { each: true })
#IsString({ each: true })
readonly emails?: string[];
#IsOptional()
#IsEnum(reportFields, { each: true })
#IsArray()
#IsString({ each: true })
readonly reportFields?: string[];
#IsOptional()
#Type(() => Number)
#IsNumber()
updatedAt?: number;
}
Controller:
#Put('reports/:id/schedule')
async updateScheduleData(
#Param('id') id: string,
#Body(new ValidationPipe()) updateData: updateScheduleDto,
) {
return this.reportService.updateScheduleData(id, updateData);
}
Service:
async updateScheduleData(id: string, updateData: updateScheduleDto) {
try {
updateData.updatedAt = this.utils.getCurrentTime();
const newData = await this.reportScheduleModel.findByIdAndUpdate(
id,
updateData,
{
new: true,
},
);
console.log(`Data has been updated to ${newData}`);
return newData;
} catch (error) {
throw new Error('>>>' + error);
}
}
But the validation not working over the keys. If I pass a non-valid key(like below) in the body object, even then the program executes without any error, how do I fix this? What am I missing?
{
"emaaaalls":["randomemail123#gmail.com"]
}
You need to pass the options { forbidUnknownValues: true } to the ValidationPipe. This will make class-validator throw an error when unknown values are passed in. You can read through the options here
You can make whitelist: true in ValidationPipe options.
When set to true, this will automatically remove non-whitelisted properties (those without any decorator in the validation class).
you can read more about this https://docs.nestjs.com/techniques/validation#stripping-properties
I have many to many association, models are Decks and Tag. I am able to console.log a JSON string with both objects, but I am unsure how to set my schema and resolver to return both in one query together, and currently receiving null. I'm using GraphQL with Sequelize on an Apollo server, with PostgreSQL as the db. What's the proper method to query this relationship in GraphQL, I presume I am not returning the data properly for GraphQL to read it?
Models
Deck.belongsToMany(models.Tag, {
through: models.DeckTag,
onDelete: "CASCADE"
});
Tag.associate = models => {
Tag.belongsToMany(models.Deck, {
through: models.DeckTag,
onDelete: "CASCADE"
});
Schema for Decks
export default gql`
extend type Query {
decks(cursor: String, limit: Int): DeckConnection!
deck(id: ID, deckname: String): Deck!
decksWithTags: [Deck!]!
}
extend type Mutation {
createDeck(deckname: String!, description: String!): Deck!
deleteDeck(id: ID!): Boolean!
}
type DeckConnection {
edges: [Deck!]!
pageInfo: DeckPageInfo!
}
type DeckPageInfo {
hasNextPage: Boolean!
endCursor: String!
}
type Deck {
id: ID!
description: String!
createdAt: Date!
user: User!
cards: [Card!]
}
`;
Resolver in question
decksWithTags: async (parent, args, { models }) => {
return await models.Deck.findAll({
include: [models.Tag]
}).then(tags => {
console.log(JSON.stringify(tags)); //able to console.log correctly
});
},
Shortened sample Console.logged JSON String
[
{
"id":1,
"deckname":"50 words in Chinese",
"description":"Prepare for your immigration interview",
***
"userId":1,
"tags":[
{
"id":1,
"tagname":"Chinese",
***
"decktag":{
***
"deckId":1,
"tagId":1
}
},
{
"id":2,
***
{
"id":2,
"deckname":"English",
***
I expect to get a result in GraphQL playground that looks similar to the JSON string.
The second week I try to link two collections in the apollo-server-express / MongoDB / Mongoose / GraphQL stack, but I do not understand how. I found a similar lesson with the REST API, what I need is called Relationships. I need this, but in GraphQL
watch video
How to add cars to the User?
I collected the test server, the code is here: https://github.com/gHashTag/test-graphql-server
Help
I have cloned your project and implemented some code and here what I changed to make relationship works. Note, I just did a basic code without validation or advance dataloader just to make sure non-complexity. Hope it can help.
src/graphql/resolvers/car-resolvers.js
import Car from '../../models/Car'
import User from '../../models/User'
export default {
getCar: (_, { _id }) => Car.findById(_id),
getCars: () => Car.find({}),
getCarsByUser: (user, {}) => Car.find({seller: user._id }), // for relationship
createCar: async (_, args) => {
// Create new car
return await Car.create(args)
}
}
src/graphql/resolvers/user-resolvers.js
import User from '../../models/User'
export default {
getUser: (_, { _id }) => User.findById(_id),
getUsers: () => User.find({}),
getUserByCar: (car, args) => User.findById(car.seller), // for relationship
createUser: (_, args) => {
return User.create(args)
}
}
src/graphql/resolvers/index.js
import UserResolvers from './user-resolvers'
import CarResolvers from './car-resolvers'
export default {
User:{
cars: CarResolvers.getCarsByUser // tricky part to link query relation ship between User and Car
},
Car:{
seller: UserResolvers.getUserByCar // tricky part to link query relation ship between User and Car
},
Query: {
getUser: UserResolvers.getUser,
getUsers: UserResolvers.getUsers,
getCar: CarResolvers.getCar,
getCars: CarResolvers.getCars
},
Mutation: {
createUser: UserResolvers.createUser,
createCar: CarResolvers.createCar,
}
}
src/graphql/schema.js
export default`
type Status {
message: String!
}
type User {
_id: ID!
firstName: String
lastName: String
email: String
cars: [Car]
}
type Car {
_id: ID
make: String
model: String
year: String
seller: User
}
type Query {
getUser(_id: ID!): User
getUsers: [User]
getCar(_id: ID!): Car
getCars: [Car]
}
type Mutation {
createUser(firstName: String, lastName: String, email: String): User
// change from _id to seller, due to base on logic _id conflict with CarId
createCar(seller: ID!, make: String, model: String, year: String): Car
}
schema {
query: Query
mutation: Mutation
}
`
src/middlewares.js
import bodyParser from 'body-parser'
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express'
import { makeExecutableSchema } from 'graphql-tools'
import typeDefs from '../graphql/schema'
import resolvers from '../graphql/resolvers'
import constants from './constants'
export const schema = makeExecutableSchema({
typeDefs,
resolvers
})
export default app => {
app.use('/graphiql', graphiqlExpress({
endpointURL: constants.GRAPHQL_PATH
}))
app.use(
constants.GRAPHQL_PATH,
bodyParser.json(),
graphqlExpress(req => ({
schema,
context: {
event: req.event
}
}))
)
}
try to make something like this in your car resolver
export default {
getCar: ({ _id: ownId }, { _id }) =>
Car.findById(ownId || _id);
// here is the rest of your code
You need to add a resolver for the cars field on the User type.
const resolvers = {
Query: {
getUsers: ...
getCars: ...
...
},
Mutation: {
...
},
User: {
cars: ...
}
}