TypeGraphQL Field Resolver with mongoose relationship - node.js

I have trouble dealing with populating the field using TypeGraphQL.
Situation summary with an example:
TypeGraphQL
TypeDef
#ObjectType()
class User {
...
}
#ObjectType()
class Post {
...
#Field()
user: User
}
Resolver
import { Post, User } from '#/models' // mongoose schema
#Resolver(() => Post)
class PostResolver {
#Query(() =>. Post)
async getPost(id: string) {
return await Post.findById(id);
}
...
#FieldReoslver(() => User)
async user(#Root() post: Post) {
return await User.findById(post.user) // Error Occurs here.
}
}
Mongoose
PostSchema
const PostSchema = new Schema({
...
user: {
type: Schema.ObjectId,
ref: "User",
}
})
I want to populate user field when the data Post is requested, with the field User type.
So, I used #FieldResolverlike above, but I encountered the Type Error because post.user is a type of User, not ObjectId of mongoose.
The user field is a type of ObjectId first when the getPost resolver was executed, but I want to populate this field to User when the client gets the response.
How can I get through this?
Thanks in advance.

Related

Mongoose with Typescript: Trying to split schema, methods and statics in seperate files, problem with this has type any and is hidden by container

i'm trying to split up my single-files mongoose schemas with statics and methods.
(I found this tutorial for splitting: https://medium.com/swlh/using-typescript-with-mongodb-393caf7adfef ) I'm new to typescript but love the benefits it gives while coding.
I've splitted my user.ts into:
user.schema.ts
user.model.ts
user.types.ts
user.statics.ts
user.methods.ts
When i change this lines in my schema file:
UserSchema.statics.findUserForSignIn = async function findUserForSignIn(
email: string
): Promise<IUserDocument | null> {
const user = await this.findOne({ email: email });
if (!user) {
return user;
} else {
return user;
}
}
to UserSchema.statics.findUserForSignIn = findUserForSignIn;
and copy the Function findUserForSignIn to user.statics.ts, Typescript says "'this' implicitly has type 'any' because it does not have a type annotation" and "An outer value of 'this' is shadowed by this container."
So, how to add this properly? If i add this to findUserForSignIn with IUserModel as Type, add null to Promise return type it would nearly work:
export async function findUserForSignIn(
this: IUserModel,
email: string
): Promise<IUserDocument | null> {
const user = await this.findOne({ "person.email": email });
return user;
}
And if i add this to receiving function parameters: users gets to type IUserDocument, before it was any. I think its nice to have typeclear, not just any.
But: in user.schema.ts the UserSchema.statics.findUserForSignIn gets a red line from typescript. Type can not be assigned to other type. The signature of this is not identical.
If i change the type of this to any, all is okay. But the return is not longer from type IUserDocument. Mabye its okay if i get over an aggregation pipeline and only set the Promise-Return-Type. But that this: any gets hinted in yellow by typescript.
And, another question: if i pass this as first and email as second parameter, why is only one parameter required?
Anyone has an "how to" for me? Or can explain what i've done wrong? Or what is the best way? Or is it not possible to split statics and methods in seperate files from schema?
Original files:
user.schema.ts
import { Schema } from "mongoose";
import { PersonSchema } from "./person.schema";
import { findUserForSignIn } from "./user.statics";
import { IUserDocument } from "./user.types";
const UserSchema = new Schema<IUserDocument>({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
});
UserSchema.statics.findUserForSignIn = findUserForSignIn;
export default UserSchema;
user.types.ts
import { Document, Model } from "mongoose";
import { IPerson } from "./person.types";
export interface IUser {
firstname: string;
lastname: string;
email: string;
}
export interface IUserDocument extends IUser, Document {}
export interface IUserModel extends Model<IUserDocument> {
findUserForSignIn: (email: string) => Promise<IUserDocument>;
}
user.model.ts
import { model } from "mongoose";
import UserSchema from "./user.schema";
import { IUserDocument, IUserModel } from "./user.types";
const User = model<IUserDocument>("User", UserSchema) as IUserModel;
export default User;
user.statics.ts
import { IUserDocument } from "./user.types";
export async function findUserForSignIn(
email: string
): Promise<IUserDocument | null> {
const user = await this.findOne({ email: email });
if (!user) {
return user;
} else {
return user;
}
}
The only way seems to change the user.statics.ts
export async function findUserForSignIn(
this: Model<IUserDocument>,
email: string
): Promise<IUserDocument | null> {
console.log("E-Mail", email);
const user = await this.findOne({ email: email });
return user;
}
this has to be of type Model
Then code seems to be okay.

Mongoose .save() returns empty error object, does not save in DB

I am trying to learn MongoDB and typescript but currently running into some issues when trying to create my first document.
When I make a post request from postman, I get "Sending request" for 5 seconds, then it times out and returns an empty error object:
{
"message": {}
}
and the posted data is not saved in my mongoDB.
I first set up connection like this in my server:
mongoose.connect(<string> process.env.DB_CONNECTION, { useNewUrlParser: true}, () =>
console.log("connected to DB!")
);
and get back the correct statement logged, so I am connected.
I have a model that looks like this:
import { model, Schema, Model, Document } from "mongoose";
export interface IUser extends Document {
name: string,
}
const UserSchema: Schema = new Schema(
{
name: {
type: Schema.Types.String,
required: true,
},
},
<any> {timeStamps: true}
);
const User: Model<IUser> = model<IUser>('User', UserSchema);
export default User;
The problem could be in there, but I don’t think it is.
Then, here is my post request for the endpoint I call:
router.post('/add', async (req: Request, res: Response): Promise<any> => {
try {
const user: IUser = new User({
name: req.body.name
});
const savedUser: IUser = await user.save();
res.status(200).json(savedUser);
} catch (err: any) {
res.status(500).json({message: err});
console.log("There was an error");
}
})
Here is where I believe the error is becuase every time the request gets stuck on awaiting the .save()
Any help would be great! Thanks!
The issue was with the initial database connection, the password contained characters that that weren't being encoded. One solution to encode special characters is to make use of the built-in encodeURIComponent() function.

Getting id in mongoose post update One hook

This is my mongoose model:
import mongoose, { Schema, Document } from "mongoose";
export interface IUserModel extends Document {
username: string;
id: string;
}
const UserSchema: Schema = new Schema({
username: { type: String },
_id: { type: String }
});
UserSchema.post('updateOne', (doc) => {
console.log("doc._id",doc._id);
});
const UserModel = mongoose.model<IUserModel>("user", UserSchema);
export default UserModel;
I need to access the unique _id in the post hook after I have called updateOne method to call some logic with that id.
However doc._id prints undefined as does doc.id.
when I console.log this it prints to the console:
{ default: Model { user } }
But when I try to access this["default"] it again gives error.
I am calling update method like this :
await UserModel.updateOne({_id: id},userModel)
mongoose version : "^5.11.10"
#types/mongoose version : "^5.10.3"
Any help will be greatly appreciated.

Cannot query field signup on type Query

Started messing around with GraphQL, but I'm stuck with this error. Not sure if it's a problem in the schema definition or in the query.
const express_graphql = require('express-graphql')
const { buildSchema } = require('graphql')
const users = require('../users/translator')
const schema = buildSchema(`
type User {
id: ID
email: String
role: String
}
type Query {
user(id: ID!): User
users: [User]
token(email: String!, password: String!): String!
}
type Mutation {
signup(email: String!, password: String!, role: String!): ID
}`
)
const resolvers = {
users: users.getAll,
user: users.getById,
token: users.login,
signup: users.create,
}
module.exports = app => {
// GraphQL route
app.use('/graphql', express_graphql({
schema,
rootValue: resolvers,
graphiql: true,
}))
}
app is an express.js server while const users holds the logic. I'm able to fetch users and tokens, but when I try to POST a mutation
{
signup(email: "my#email.com", password: "321321", role: "admin")
}
I get the error Cannot query field "signup" on type "Query". By looking at the GraphiQL suggestions after reading the schema from the server, it looks like the signup mutation doesn't even get exported:
Some tutorials say I should export resolvers using
const resolvers = {
query: {
users: users.getAll,
user: users.getById,
token: users.login,
},
mutation: {
signup: users.create,
}
}
But it doesn't work either. Any hints?
You need to specify the operation type (query, mutation or subscription) like this:
mutation {
signup(email: "my#email.com", password: "321321", role: "admin")
}
If the operation type is omitted, the operation is assumed to be a query. This is called "query shorthand notation", but only works if your operation is unnamed and does not include any variable definitions.
It's good practice to always include the operation type regardless.

MongoDB - Error: document must have an _id before saving

I've been struggling so much with this project. I am following a tutorial that is out of date in some areas, for instance their version of Jquery used a totally different format for some functions and I had to do a lot of changing around. But I think I am down to one last major problem that I can't seem to find a fix for. In my Schema variable I've got the _id, username, and password types
var UserSchema = new mongoose.Schema({
_id: mongoose.Schema.ObjectId,
username: String,
password: String
});
but when I go to try to add a new user to my app, instead of getting the alert I am supposed to get, it pops up as [object Object] and nothing gets added to the database. Then this error pops up in the mongo cmd
"Error: document must have an _id before saving".
I've tried commenting out the _id line and I get the right message but still nothing shows up in my database.
Its pretty simple:
If you have declared _id field explicitly in schema, you must initialize it explicitly
If you have not declared it in schema, MongoDB will declare and initialize it.
What you can't do, is to have it in the schema but not initialize it. It will throw the error you are talking about
NestJS with mongoose (#nestjs/mongoose) solution
I fixed the error by
Removing #Prop() above _id
Add mongoose.Types.ObjectId as type to _id
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import mongoose from 'mongoose';
import { Document } from 'mongoose';
export type CompanyDocument = Company & Document;
#Schema()
export class Company {
_id: mongoose.Types.ObjectId;
#Prop({ unique: true })
name: string;
}
export const CompanySchema = SchemaFactory.createForClass(Company);
You can write your model without _id so it will be autogenerated
or
you can use .init() to initialize the document in your DB.
Like:
const mongoose = require('mongoose');
const UserSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
username: String,
password: String
})
module.exports = mongoose.model('User', UserSchema);
and then
const User = require('../models/user');
router.post('/addUser',function(req,res,next){
User.init() // <- document gets generated
const user = new User({
username: req.body.username,
password: req.body.password
})
user.save().then((data)=>{
console.log('save data: ',data)
// what you want to do after saving like res.render
})
}
If you are using mongoose with nest js and GraphQL, I have fixed it by changing the id to _id and removing the #prop above it even the null value of the id problem has vanished. example on github
import { ObjectType, Field, Int, ID } from '#nestjs/graphql';
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
import { User } from 'src/user/entities/user.entity';
import * as mongoose from 'mongoose';
export type SchoolDocument = School & Document;
#ObjectType()
#Schema()
export class School {
#Prop()//remove this
#Field(() => ID,{ nullable: true })
_id: string;
#Prop()
#Field(() => String,{ nullable: true })
name: string;
#Field(()=>[User],{nullable:true})
users:User[];
}
export const SchoolSchema= SchemaFactory.createForClass(School);
Try below snippet I wanted to name _id as userId you can do without it as well.
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var UserSchema = new Schema({
username: String,
password: String
});
UserSchema.virtual('userId').get(function(){
return this._id;
});
_id is added automatically by MongoDb.
If you want to keep _id on your data structure be sure to initialize correctly:
import { Types } from "mongoose";
const obj = new UserSchema({
"_id": new Types.ObjectId(),
"username": "Bill",
"password" : "...."
});
In my case, I accidentally had the following at the end of my Schema. Removing that worked:
{ _id: false }
Look the way i fixed was i put just id in json post request and not _id.
No need to specify the document _id in your model. The system generates the id automatically if you leave out the _id like so:
var UserSchema = new mongoose.Schema({
username: String,
password: String
});
That being said, if you still want to generate the _id yourself, see the answers above.

Resources