I tried to joiful validation using mongodb objectId.but its throwing error Property 'ObjectId' does not exist on type 'typeof import("/home/lenovo/Music/basic/node_modules/joiful/index")'
import * as jf from "joiful";
import {ObjectId} from 'mongodb';
class SignUp {
#jf.string().required()
username?: string;
#jf
.string()
.required()
.min(8)
password?: string;
#jf.date()
dateOfBirth?: Date;
#jf.boolean().required()
subscribedToNewsletter?: boolean;
#jf.ObjectId().required()
id?:ObjectId;
}
const signUp = new SignUp();
signUp.username = "rick.sanchez";
signUp.password = "wubbalubbadubdub";
const { error } = jf.validate(signUp);
Is it possible to validate objectId using joiful.
I know that this question is along time ago, and the library maintainers didn't add this validator yet, for that I created a custom decorator that uses joiful custom method to make custom validation
import * as jf from 'joiful';
import Joi from 'joi';
import { ObjectId } from 'mongodb';
export const objectIdValidationDecorator = () => jf.any().custom(({ schema }: { schema: Joi.Schema }) => {
return schema.custom((value, helpers) => {
const objectId = new ObjectId(value);
if (objectId.equals(value)) {
return objectId;
} else {
return helpers.error('any.invalid');
}
});
})
Usage:
class MyObj {
#objectIdValidationDecorator().required()
referenceId:ObjectId
}
Related
I am creating application where I am using NestJs framework and using MongoDB database with Mongoose ORM. I have nested data structure that I am trying to save inside database, but it's throwing error when I am saving it.
Below is my error:
[Nest] 11196 - 19/02/2022, 6:01:30 pm ERROR [ExceptionsHandler] Cannot set property 'city' of undefined
TypeError: Cannot set property 'city' of undefined
at UserService.addUser (D:\nest\nested-schema-proj\src\user\user.service.ts:18:27)
at UserController.addUser (D:\nest\nested-schema-proj\src\user\user.controller.ts:11:33)
Below is my Postman request:
When I am posting data as raw JSON that can be seen in screenshot below then it is added successfully. Then why it not adding using first way?
Below is my code:
user.schema.ts
import mongoose from "mongoose";
const addressSchema = new mongoose.Schema({
city:{type:String},
state:{type:String}
});
export const UserSchema = new mongoose.Schema({
name:{type:String},
age:{type:Number},
address:addressSchema
});
interface Address{
city:string;
state:string;
}
export interface AllUser{
name:string;
age:number;
address:Address;
}
user.dto.ts
export class UserDto{
name:string;
age:number;
address:Address
}
class Address{
city:string;
state:string;
}
user.controller.ts
import { Body, Controller, Post } from '#nestjs/common';
import { UserDto } from './dto/user.dto';
import { UserService } from './user.service';
#Controller()
export class UserController {
constructor(private userService:UserService){}
#Post('addUser')
addUser(#Body() userDto:UserDto){
return this.userService.addUser(userDto);
}
}
user.service.ts
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model} from 'mongoose';
import { UserDto } from './dto/user.dto';
import { AllUser } from './schema/user.schema';
#Injectable()
export class UserService {
constructor(#InjectModel('AllUser') private readonly userModel:Model<AllUser>){}
async addUser(userDto:UserDto): Promise<AllUser>{
console.log(userDto);
const data = new this.userModel();
data.name = userDto.name;
data.age = userDto.age;
data.address.city = userDto.address.city; //Showing error in this line
data.address.state = userDto.address.state;
const result = await data.save();
return result;
//Posting data as a raw JSON as shown in 2nd screenshot
// const data = new this.userModel(userDto);
// const result = await data.save();
// return result;
}
}
What am I doing wrong?
You cannot save because the user model is interface object which is injected on initialisation of your service. Also you cannot this model by initiating and access its property.
Instead you can expand the DTO and save it. Also you can manipulate you extra fields if you want from your code. In below example I have added date/time of document creation
return await new this.userModel({
...userDto,
created_at: moment.utc() //Manipulate your extra fields and set here
}).save();
Also you can set you own DTO object if you need to again maipulate data on controller level and pass that DTO direclty to service and just save it
For example:
//In Controller
const data = new UserDto();
data.name = userDto.name;
data.age = userDto.age;
data.address.city = userDto.address.city;
data.address.state = userDto.address.state;
data.created_at: new Date();
return await this.userService.addUser(data);
//Service:
async addUser(userDto:UserDto): Promise<AllUser>{
console.log(userDto);
return await new this.userModel({
...userDto,
created_at: moment.utc() //Manipulate your extra fields and set here
}).save();
}
use Address.city in postman, will definitely work
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.
My function save of an object (in model.ts file) which is created by typegoose should return Promise<Todo> but it returns Promise<Document<any, {}>>
and i have this error :
Type 'Document<any, {}>' is missing the following properties from type 'Todo': createdAt, updatedAt, content, isDone
How should i correct this ?
model ts
import { getModelForClass } from "#typegoose/typegoose";
import { ObjectId } from "mongodb";
import { Todo } from "../../entities";
import { NewTodoInput } from "./input";
// This generates the mongoose model for us
export const TodoMongooseModel = getModelForClass(Todo);
export default class TodoModel {
async getById(_id: ObjectId): Promise<Todo | null> {
// Use mongoose as usual
return TodoMongooseModel.findById(_id).lean().exec();
}
async create(data: NewTodoInput): Promise<Todo> {
const todo = new TodoMongooseModel(data);
return todo.save();
}
}
input ts
import { Field, InputType, ID } from "type-graphql";
import { MaxLength, MinLength } from "class-validator";
#InputType()
export class NewTodoInput {
#Field()
#MaxLength(300)
#MinLength(1)
content: string | undefined;
}
entities todo ts
import { ObjectType, Field } from "type-graphql";
import { prop } from "#typegoose/typegoose";
import { ObjectId } from "mongodb";
#ObjectType()
export class Todo {
#Field()
readonly _id!: ObjectId;
#prop()
#Field(() => Date)
createdAt!: Date;
#prop()
#Field(() => Date)
updatedAt!: Date;
#prop()
#Field()
content!: string;
#prop({ default: false })
#Field()
isDone!: boolean;
}
Thank you.
with the unofficial mongoose types, it is an known problem that await document.save() does not return the correct typings
-> this is not an problem with typegoose, it is with #types/mongoose
the workaround is to use:
await doc.save();
return doc;
(or maybe in your case directly use the mongoose provided function .create)
PS: this is not a problem in the official types of mongoose (can be currently used with typegoose 8.0.0-beta.x)
I have a project in nodejs and typescript. I'm using mongoose to connect to a mongoDb database. My code looks like this
import { Schema, Document, Model } from 'mongoose';
import * as mongoose from 'mongoose';
export interface IProblem extends Document {
problem: string;
solution: string;
}
const ProblemSchema = new Schema({
problem: { type: String, required: true },
solution: { type: String, required: true },
});
export async function findOneByProblem(
this: IProblemModel,
{ problem, solution }: { problem: string; solution: string }
): Promise<IProblem> {
const record = await this.findOne({ problem, solution });
return record;
}
export default mongoose.model('Problem', ProblemSchema);
ProblemSchema.statics.findOneByProblem = findOneByProblem;
export interface IProblemModel extends Model<IProblem> {
findOneByProblem: (
this: IProblemModel,
{ problem, solution }: { problem: string; solution: string }
) => Promise<IProblem>;
}
However, at these lines
const record = await this.findOne({ problem, solution });
return record;
I get a compiler error saying this
TS2322: Type 'IProblem | null' is not assignable to type 'IProblem'. Type 'null' is not assignable to type 'IProblem'.
Am I missing something?
Your type for findOneByProblem is wrong – after all, it's possible that you don't find an IProblem instance, and the result is null.
The correct type is
Promise<IProblem | null>
– or you could internally if(problem === null) throw new Error("No Problem found"); or similar in the function if you don't want to change the type.
Well, newbie with NodeJS and Mongoose, will try to get a document from a collection and use a class to manage results as i want to do.
I followed many tutos, but... i don't understand why, with the following code, i always get a null object with a findById() method on a model.
After hours spent, i decide to get help...
So, first i define a Model (simplified version) :
import { Schema, Model, model } from 'mongoose';
import { DocumentInterface } from './../interfaces/document-product-interface';
import { ProductClass } from './product-class';
var productSchema: Schema = new Schema(
{
_id: {
type: String,
required: 'Required _id'
},
id: {
type: String,
required: 'EAN required'
},
product_name: {
type: String
}
}
);
// Scheme methods
productSchema.method('title', ProductClass.prototype.title);
export const Products: Model<DocumentInterface> = model<DocumentInterface>('ProductClass', productSchema);
Next, create a class (for business purpose) :
import { ProductInterface } from './../interfaces/product-interface';
export class ProductClass implements ProductInterface{
public _id: String;
public id: String;
public product_name_fr?: string;
public product_name?: string;
public constructor() {}
public title(): string {
return 'something';
}
}
Next... The Document interface :
import { Document } from 'mongoose';
import { ProductInterface } from './product-interface';
import { ProductClass } from 'models/product-class';
export interface DocumentInterface extends ProductInterface, Document, ProductClass {}
Finally, just a controller, to get a product :
import { Products } from '../models/product-model';
import { SoappliProductInterface } from './../interfaces/soappli-product-interface';
import { Request, Response, NextFunction} from 'express';
export class ProductController {
public get(request: Request, response: Response, next: NextFunction) {
console.log('Search for a product : ' + request.params.ean);
Products.findById(request.params.ean, (error: any, product: DocumentInterface) => {
if (error) {
response.status(500).send( {message: error})
} else {
console.log('Product: ' + JSON.stringify(product));
if (product) {
console.log('Product title: ' + product.title);
response.status(200).send(product);
} else {
response.status(404).send({message: 'no matches for : ' + request.params.ean})
}
}
});
}
}
When i run with Postman and use a correct _id, got null for product...
If i remove all classes and interfaces, get the entire document...
What's wrong with this code ?
Thx