Can not Query all users because of MongoDB id - mongodb

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

Related

Is there any way to get the list of validators used in moongoose schema?

I want to get the list of validators that is used in a moongoose schema? Something like
const userModel = {
firstName: {
type:String,
required: true
}
}
// is there any method to get validations like
console.log(userModel.getValidators())
Output:
{
firstName: {required: true ....etc},
}
Once you setup your model using the SchemaFactory.createForClass method from a class with a #Schema decorator as shown in the docs, you can export the schema. If you then, import the schema and access its obj property, you can extract information about the field.
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
#Schema()
export class Cat {
#Prop({ required: true })
name: string;
#Prop()
age: number;
#Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
import { CatSchema } from './cat.schema';
console.log(CatSchema.obj.name.required); // true
console.log(CatSchema.obj.age.type.name); // 'Number'

How Flutter Graphql not mapping to DTO

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.

Error retrieving data from DB using typeorm and type-graphql

I'm using type-graphql in conjunction with typeorm, apollo-server-express and postgreSQL. I have a User and a Customer entity in a 1:n relationship, meaning one user can have multiple customers.
I can create users and customers just fine, but when attempting to retrieve the user associated to a customer using Apollo Server playground, I get an error message stating "Cannot return null for non-nullable field Customer.user."
When I check the database, the associated user id on the customer table is definitely not null (see attached image).
query {
customers {
customerId
customerName
user {
userId
}
}
}
Does anyone know what I'm doing wrong?
User.ts
import { Field, ID, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { Customer } from "./Customer";
#ObjectType()
#Entity("users")
export class User extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn("uuid")
userId: string;
#Field()
#Column({ unique: true })
email: string;
#Column({ nullable: false })
password: string;
#Field(() => Customer)
#OneToMany(() => Customer, customer => customer.user)
customers: Customer[]
}
Customer.ts
import { Field, ID, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { User } from "./User";
#ObjectType()
#Entity("customers")
export class Customer extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn("uuid")
customerId: string;
#Field()
#Column()
customerName: string;
#Field(() => User)
#ManyToOne(() => User, user => user.customers)
user: User;
}
CustomerResolver.ts
export class CustomerResolver {
#Query(() => [Customer])
async customers():Promise<Customer[]> {
try {
return await Customer.find();
} catch (error) {
console.log(error);
return error;
}
}
....
Setup / Version
Node: v14.17.0
"apollo-server-express": "^2.24.0",
"type-graphql": "^1.1.1",
"typeorm": "0.2.32"
postgreSQL: 13.2
In your resolver change the find operation like below:
return Customer.find({
relations: ["user"]
});
You should write a #FieldResolver which will fetch customers based on root user data.
https://typegraphql.com/docs/resolvers.html#field-resolvers

Type 'string' is not assignable to type 'Condition<UserObj>' when making mongoose query by ID

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.

How should I define interfaces of documents when using Typescript and Mongodb?

Consider a simple user collection:
// db.ts
export interface User {
_id: mongodb.ObjectId;
username: string;
password: string;
somethingElse: string;
}
// user.ts
import {User} from "../db"
router.get("/:id", async (req, res) => {
const id = req.params.id;
// user._id is a mongodb.Object.
const user: User = await db.getUser(id);
res.send(user);
});
// index.ts
// code that will runs on browser
import {User} from "../../db"
$.get('/user/...').done((user: User) => {
// user._id is string.
console.log(user._id);
});
It works perfectly until I want to use this interface in client codes. Because the _id of user becomes a hex string when tranmitted as json from server. If I set _id to be mongodb.ObjectId | string, the behavior gets wierd.
You can try to separate them in a smart way :
interface User {
username: string;
password: string;
somethingElse: string;
}
export interface UserJSON extends User {
_id : string
}
export interface UserDB extends User {
_id : mongodb.ObjectId
}
and later take either UserJSON ( client ) or UserDB ( server-side ).
Thanks to #drinchev. And I have figured out a better way to do it, using generics:
interface User<IdType> {
_id: IdType;
username: string;
posts: Post<IdType>[];
}
interface Post<IdType> {
_id: IdType;
text: string;
}
export type UserDB = User<mongodb.ObjectID>;