How do I refer to the _id field in a mongodb database via a REST API GET call? - mongodb

This is hard to explain, so please bear with me.
I have a Nestjs-based server that is using MongoDB as the back end. Here are the three entries from the mongodb collection:
{"_id":{"$oid":"5e87ef7a9cf8648fac6b9f1e"},"complete":false,"editMode":false,"createdAt":{"$date":{"$numberLong":"1585966970857"}},"createdBy":"user","isDeleted":false,"title":"Make a birthday cake","note":"Make sure she poops."}
{"_id":{"$oid":"5e87f081237c70a6782d6c2a"},"createdAt":{"$date":{"$numberLong":"1585967233825"}},"createdBy":"user","isDeleted":false,"complete":false,"editMode":false,"title":"Clean the kitchen","note":"Use Lysol"}
{"_id":{"$oid":"5e87f73be81d7e0061311187"},"createdAt":{"$date":{"$numberLong":"1585968955971"}},"createdBy":"user","isDeleted":false,"complete":false,"editMode":false,"title":"Walk the dog","note":"Make sure she poops."}
Here is my model:
#Entity()
export class Todo {
#ObjectIdColumn()
#Transform(value => value.toString(), { toPlainOnly: true })
id: ObjectID;
#Column({ length: 100 })
title: string;
#Column({ length: 5000 })
note: string;
#Column()
complete: boolean;
#Column()
editMode: boolean;
#Exclude() #Column() createdAt: Date = new Date();
#Exclude() #Column() createdBy: string = 'user';
#Exclude() #Column() isDeleted: boolean = false;
}
Here is my GET-er stuff:
#Get(':id')
async getTodo(#Param('id') id: number) {
return this.todosService.getTodo(id);
}
async getTodo(id: number): Promise<Todo | undefined> {
return this.todosRepository.findOne(id, {
where: {
isDeleted: false,
},
});
}
I would like to run a REST GET call to retrieve one of the documents, say the third one. Therefore I call in my browser:
http://localhost:3000/todos/5e87f73be81d7e0061311187
Well, this returns the first document. In fact, anything I call only returns the first document.
What should my GET call be to get the third item?
I can provide any further information that might be needed.

Firstly, i think the id parameter (id: number), should be a string.
Also, findone takes all the querying parameter in first argument itself ( atleast in JS), so it should be like todoRepository.findOne({_id .$oid: string, isDeleted: false }).
Can you tell me what's $oid? ObjectId type? I haven't worked with nest.js/ typescript. Depending upon this you might want to improve the $oid in query argument.

Here's the answer: Make the id parameter an ObjectID type everywhere in the application.
For instance:
#Get(':id')
async getTodo(#Param('id') id: ObjectID) {
return this.todosService.getTodo(id);
}
async getTodo(id: ObjectID): Promise<Todo | undefined> {
return this.todosRepository.findOne(id, {
where: {
isDeleted: false,
},
});
}
Once I did that, it all worked as expected.

Related

TypeORM selecting N random rows from table

I'm trying to select a certain number of Questions from my postgres database using TypeORM 0.3.11 with NestJS.
I tried using the createQueryBuilder to create that specific SQL query, but I keep getting the exception: "for SELECT DISTINCT, ORDER BY expressions must appear in select list".
So, I wrote next method, which should (by my interpretation) select rows that have corresponding quiz ID, load answers, "shuffle" questions and take only N questions (20 in this example).
async findByQuizAndServeRandomlyQuery(quiz: Quiz): Promise<Question[]> {
const questions = await this.questionRepository
.createQueryBuilder()
.setFindOptions({
where: {
quiz: {
id: quiz.id,
},
},
relations: {
answers: true,
},
})
.orderBy('RANDOM()')
.take(20)
.getMany();
return questions;
}
I checked, the quiz variable is defined, RANDOM() is available in postgres.
When the method is called, next exception is thrown:
QueryFailedError: for SELECT DISTINCT, ORDER BY expressions must appear in select list
I'm not sure why DISTINCT is included here.
[EDIT] Here is my question entity schema:
import { Exclude, Expose } from 'class-transformer';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Quiz } from './quiz.entity';
#Entity('quiz-questions')
export class Question {
constructor(partial?: Partial<Question>) {
Object.assign(this, partial);
if (!this.positivePoints) this.positivePoints = 1;
if (!this.negativePoints) this.negativePoints = 0;
}
#Expose()
#PrimaryGeneratedColumn()
id: number;
#Exclude()
#ManyToOne(() => Quiz, (quiz) => quiz.questions)
#JoinColumn()
quiz: Quiz;
#Expose()
#Column()
text: string;
#Expose()
#Column({ default: 1 })
positivePoints: number;
#Expose()
#Column({ nullable: true })
negativePoints: 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.

Can not Query all users because of MongoDB id

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

How can I create a query on nested documents?

I am trying to find a document inside nested collection, but I do not know how should I create query for this. In TypeORM documentation there is only one simple example how to use find/findOne on not nested documents.
I created this query:
const result: BlogEntity = await this.blogRepository.findOne({posts: {_id: article._id}});
but when I am trying to build my project i am getting this error:
error TS2345: Argument of type '{ posts: { _id: ObjectID; }; }' is not assignable to parameter of type 'string | number | Date | ObjectID | FindOneOptions | Partial'.
After this i also tried :
const result: BlogEntity = await this.blogRepository.findOne({posts: {_id: article._id}});
const result: BlogEntity = await this.blogRepository.findOne({'posts._id': article._id});
const result: BlogEntity = await this.blogRepository.findOne({where: {posts: { _id: article._id}}});
const result: BlogEntity = await this.blogRepository.findOne({where: {'posts._id': _id: article._id}});
const result: ArticleEntity = await this.mongoManager.findOne(ArticleEntity, {_id: article._id});
But none of them is working
So the question is how should i correctly create this find query. BlogEntity and ArticleEntity code bellow
BlogEntity
#Entity()
#ObjectType()
export class BlogEntity {
#Field(() => ID)
#ObjectIdColumn()
_id!: ObjectID;
#Field(() => [ArticleEntity])
#Column()
posts!: ArticleEntity[];
#Field(() => [ArticleEntity])
#Column()
projects!: ArticleEntity[];
#Field(() => [ArticleEntity])
#Column()
tutorials!: ArticleEntity[];
}
ArticleEntity
#Entity()
#ObjectType()
export class ArticleEntity {
#Field(() => ID)
#ObjectIdColumn()
_id!: ObjectID;
#Field()
#Column()
title!: string;
#Field(() => [String])
#Column()
tags!: string[];
#Field()
#Column()
release!: Date;
#Field(() => [SectionEntity])
#Column()
sections!: SectionEntity[];
#Field({ nullable: true })
#Column()
tutorial?: string;
#Field({ nullable: true })
#Column()
app?: string;
}
If you need anything else ping me in the comment section.
When you are working with nested object's you need to use (.) operator
await this.blogRepository.findOne({'posts._id': article._id});
So it is impossible to achive this in TypeORM. The only one solution which I see there is to swich to Typegoose or Mongoose.
If you are more interested in this issue you can read more here: Issue: Query an Array of Embedded Documents #2483

Mongoose & TypeScript - Property '_doc' does not exist on type 'IEventModel'

I'm learning some JavaScript backend programming from a course I'm taking. It focuses on ExpressJS, MongoDB, and GraphQL. Because I like making things more challenging for myself, I decided to also brush up on my TypeScript while I'm at it by doing all the coursework in TypeScript.
Anyway, so I'm using verison 5.5.6 of mongoose and #types/mongoose. Here is my interface for the type of the DB record:
export default interface IEvent {
_id: any;
title: string;
description: string;
price: number;
date: string | Date;
}
Then I create the Mongoose Model like this:
import { Document, Schema, model } from 'mongoose';
import IEvent from '../ts-types/Event.type';
export interface IEventModel extends IEvent, Document {}
const eventSchema: Schema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
date: {
type: Date,
required: true
}
});
export default model<IEventModel>('Event', eventSchema);
Lastly, I have written the following resolver for a GraphQL mutation:
createEvent: async (args: ICreateEventArgs): Promise<IEvent> => {
const { eventInput } = args;
const event = new EventModel({
title: eventInput.title,
description: eventInput.description,
price: +eventInput.price,
date: new Date(eventInput.date)
});
try {
const result: IEventModel = await event.save();
return { ...result._doc };
} catch (ex) {
console.log(ex); // tslint:disable-line no-console
throw ex;
}
}
My problem is that TypeScript gives me an error that "._doc" is not a property on "result". The exact error is:
error TS2339: Property '_doc' does not exist on type 'IEventModel'.
I can't figure out what I'm doing wrong. I've reviewed the documentation many times and it seems that I should have all the correct Mongoose properties here. For the time being I'm going to add the property to my own interface just to move on with the course, but I'd prefer help with identifying the correct solution here.
This might be a late answer but serves for all that come searching this.
inteface DocumentResult<T> {
_doc: T;
}
interface IEvent extends DocumentResult<IEvent> {
_id: any;
title: string;
description: string;
price: number;
date: string | Date;
}
Now when you call for (...)._doc , _doc will be of type _doc and vscode will be able to interpert your type. Just with a generic declaration. Also instead of creating an interface for holding that property you could include it inside IEvent with the type of IEvent.
This is something that I do when I always use typescript alongside mongoose,
first things first we should define interfaces for the schema and model:
export interface IDummy {
something: string;
somethingElse: string;
}
export interface DummyDocument extends IDummy, mongoose.Document {
createdAt: Date;
updatedAt: Date;
_doc?: any
}
second we should create out schema:
const DummySchema = new mongoose.Schema<DummyDocument>({
something: String,
somethingElse: String,
})
finally we are going to use export model pattern for exporting our model as a module from file:
export const DummyModel = mongoose.model<DummyDocument>
Now the problem has fixed and you are not going to see the typescript error, we have manually attached the _doc to our model with generics that the aid of generics.
interface MongoResult {
_doc: any
}
export default interface IEvent extends MongoResult {
_id: any;
title: string;
description: string;
price: number;
date: string | Date;
}
Then you still have to deal with casting the _doc back to your own IEvent...
add _doc with type any to your custom Model interface
interface IUser extends Document {
...
_doc: any
}
a full example is here https://github.com/apotox/express-mongoose-typescript-starter
The _doc field will be a circular reference. So an easy way to go about it is to simply do something like this.
It also avoids infinite circular references by omitting itself in the child record.
No interface extension is required!
export default interface IEvent {
_doc: Omit<this,'_doc'>;
}
For some reasons, the structure of the return type is not included in #types/mongoose lib. So each time you want to de-structure the return object you get an error that the variable in not definition in the interface signature of both document and your custom types. That should be some sort of bug i guess.
The solution is to return the result itself, that will automatically return the data defined in interface (IEvent) without the meta data .
...
try {
const result = await event.save();
return result;
} catch (ex) {
throw ex;
}
...