I have the following Schema:
import Mongoose from 'mongoose'
const ThingSchema = new Mongoose.Schema({
name: {
type: String
},
traits: {
type: Object
}
})
const Thing = Mongoose.model('Thing', ThingSchema)
export default Thing
The first time I created such a document and saved it in the DB I set a name: 'something' and a traits: { propA: 1, propB: 2 } and everything was saved correctly.
Now I want to update the document and set a new property inside traits:
let thingInDB = await ThingModel.findOne({ name: 'something' })
console.log(thingInDB.traits) // <-- it logs { propA: 1, propB: 2 } as expected
thingInDB.traits.propC = 3
await thingInDB.save()
The above code is executed with no errors but when I look in the DB the new propC is not saved in traits. I've tried multiple times.
Am I doing something wrong ?
Have you tried using thingSchema.markModified("trait") before the .save() method? It worked for me when I ran into a similar problem in the past.
I had to declare every property of the object explicitly:
import Mongoose from 'mongoose'
const ThingSchema = new Mongoose.Schema({
name: {
type: String
},
traits: {
propA: {
type: Number
},
propB: {
type: Number
},
propC: {
type: Number
}
}
})
const Thing = Mongoose.model('Thing', ThingSchema)
export default Thing
Related
I'm making 3 schemas (article, comment, user) and models that share some fields.
FYI, I'm working with mongoose and typescript.
mongoose v6.1.4
nodejs v16.13.1
typescript v4.4.3
interface of each 3 schema shares a common interface UserContent, and they looks like this:
interface IUserContent {
slug: string;
should_show: 'always' | 'never' | 'by_date';
show_date_from: Date | null;
show_date_to: Date | null;
published_date: Date | null;
}
interface IArticle extends IUserContent {
title: string;
content: string;
user_id: number;
}
interface IComment extends IUserContent {
content: string;
user_id: number;
}
interface IUser extends IUserContent {
name: string;
description: string;
}
And I'm trying to make an function which creates Mongoose Schema with shared fields:
import { Schema, SchemaDefinition } from 'mongoose'
const createUserContentSchema = <T extends object>(fields: SchemaDefinition<T>) => {
const schema = new Schema<IUserContent & T>({
// theese fields are shared fields
slug: { type: String },
should_show: { type: String, enum: ['always', 'never', 'by_date'] },
show_date_from: { type: Date },
show_date_to: { type: Date },
published_date: { type: Date },
// this is not-shared fields
...fields,
})
return schema
}
I was expected that this function will create schema with shared fields and non-shared fields combined together. (like code below)
const UserSchema = createUserContentSchema<IUser>({
name: {type: String},
description: {type: String},
});
However, It throws Type Error on the object parameter in new Schema which is inside createUserContentSchema function. (nevertheless compiled javascript code works well as expected)
Type '{ slug: { type: StringConstructor; }; should_show: { type: StringConstructor; enum: string[]; }; show_date_from: { type: DateConstructor; }; show_date_to: { ...; }; published_date: { ...; }; } & SchemaDefinition' is not assignable to type 'SchemaDefinition<SchemaDefinitionType<IUserContent & T>>'.ts(2345)
I removed generic from createUserContentSchema function and directly replaced T to IUser and it turns out to be nice without error. So, I'm assuring that I made mistake in typing generic. but can't figure out what did I make wrong exactly.
I want to fix my code to not make this Type Error.
PS
I found out that my error is reproduced only in mongoose#v6 (not v5)
I read the breaking changes in update note but can't figure why this error is being produced in v6.
Mongoose Discriminator sounds like a feature you need.
https://mongoosejs.com/docs/api.html#model_Model.discriminator
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: String,
createdAt: Date
});
}
util.inherits(BaseSchema, Schema);
const PersonSchema = new BaseSchema();
const BossSchema = new BaseSchema({ department: String });
const Person = mongoose.model('Person', PersonSchema);
const Boss = Person.discriminator('Boss', BossSchema);
new Boss().__t; // "Boss". `__t` is the default `discriminatorKey`
const employeeSchema = new Schema({ boss: ObjectId });
const Employee = Person.discriminator('Employee', employeeSchema, 'staff');
new Employee().__t; // "staff" because of 3rd argument above
I had implemented a typescript code for a crud API but currently, I'm facing an issue while inserting data using API using the mongoose package. AS my database is MongoDB so I have used this package.
import Transaction from 'mongoose-transactions-typescript';
I am working on typescript project so that's why I have used this package
public async createAffiliateUser(res_data: any){
console.log("Function");
const transaction = new Transaction();
console.log("Function123");
const AffiliateUserModelName = 'affiliateusers'; // COLLECTION name
console.log(res_data);
await transaction.insert(AffiliateUserModelName, {
name: res_data.name,
userName: res_data.userName,
groupId: res_data.groupId,
commissionCount: res_data.commissionCount,
commissionAmount: res_data.commissionAmount,
is_active: res_data.is_active
});
return await transaction.run();
}
In the above code highlighted line throwing an error like this
TypeError:mongoose_transactions_typescript_1.default is not a constructor
In the above function when I tried to use default create method of mongoose it inserting only single column data even though passing full data as below
{
"name": "Test",
"userName":"test123",
"groupId": "1",
"commissionCount": 1,
"commissionAmount": 2,
"is_active": true
}
So if anyone knows how to insert data in MongoDB using typescript or a solution for the above problem then pls help me to resolve this?
Thank you
I don't know why you are this code structure in order to use mongoose.
The steps that I follow in order to use correctly mongodb documents with mongoose are these:
create a mongoose model schema like this:
// I usually like create this file in a database folder
import mongoose, { Document, Model } from "mongoose";
const Schema = mongoose.Schema;
// creating the actual mongoose schema
const UserSchema = new Schema(
{
firstName: {
type: String,
},
lastName: {
type: String,
},
username: {
type: String,
},
lang: {
type: String,
default: "it",
},
},
{ timestamps: true }
);
// exporting the type in order to have all the correct linting
export interface IUser extends Document {
id: string;
firstName: string;
lastName?: string;
username?: string;
createdAt: Date | number;
updatedAt: Date | number;
}
// registering in mongoose models the schema with the relative interface
const User =
(mongoose.models.User as Model<IUser>) ||
mongoose.model<IUser>("User", UserSchema);
export default User;
at this point let's suppose that you have a tree similar to this:
root_folder
|-- database
| |-- User.ts
|
|-- controllers
|-- addUser.ts
creating the document in the collection:
import { User } from "../../database/User.ts"
async function addUser(){
const newUser = await new User({firstName: "foo", lastName: "bar", username:"testUser"}).save()
}
and now you should have your fresh document in the users collection
Let's say I have two schemas:
Foo.ts
import mongoose, { Schema, Document } from 'mongoose';
export interface IFoo extends Document {
name: string;
}
const fooSchema = new Schema(
{
name: {
type: String,
required: true,
}
}
);
export default mongoose.model<IFoo>('Foo', fooSchema);
And Bar.ts
import mongoose, { Schema, Document } from 'mongoose';
export interface IBar extends Document {
fooId: string | IFoo; // can be string or can be Foo document
}
const barSchema = new Schema(
{
fooId: {
type: Schema.Types.ObjectId,
ref: 'Foo',
required: true,
},
title: String;
}
);
export default mongoose.model<IBar>('Bar', barSchema);
Now when I find a Bar document with Foo populated. I get a compilation error from typescript
const bar = await Bar.findOne({ title: 'hello' }).populate({ path: 'fooId', model: 'Foo' });
bar.fooId.name // here typescript gives an error
The error is
Property 'name' does not exist on type 'string'
Since I've defined in IBar that fooId can be string | IFoo. Why typescript is complaining? And how to solve it?
If you check typings of the populate method you can see that it's result type is simply this. So, before addressing name field of the fooId property you have to explicitly narrow the type of the result to IFoo type:
const bar = await Bar.findOne({ title: 'hello' })
.populate({ path: 'fooId', model: 'Foo' });
if (typeof bar.fooId !== 'string') { // discards the `string` type
bar.fooId.name
}
Or just simply type assert the result:
const bar = await Bar.findOne({ title: 'hello' })
.populate({ path: 'fooId', model: 'Foo' });
(bar.fooId as IFoo).name
I have been given some code to modify. It is a Node.js app using Mongoose to interact with a MongoDb instance. In Mongoose several schemas were already set up and I've added a few. Among those are these two schemas which break apart a previously existing schema (which was working fine with small data):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedProjectSchema = new Schema(
{
project_id : {
type: String,
default: ""
},
dataset_id : {
type: String,
default: ""
},
properties:{
type: {},
default: {}
}
});
MapConvertedProjectSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedProject', MapConvertedProjectSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedLayerSchema = new Schema(
{
parent_id:
{
type: mongoose.Schema.Types.ObjectId
},
class:
{
type: String,
default: 'MapLayer',
trim: true
},
properties:
{
type: {},
default: {}
}
});
//Hook a pre save method to clean date
MapConvertedLayerSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedLayer', MapConvertedLayerSchema);
I use the MapConvertedLayer schema like so:
var mongoose = require('mongoose');
var LayerConverted = mongoose.model('MapConvertedLayer');
var newLayer = new LayerConverted();
//newLayer._id is automatically populated with a value
//... add other properties
newLayer.save(function(err)
{
if(err)
{
//...
}
});
This works without any issues that I can discern. However if I try similar code with MapConvertedProject I get an error:
var mongoose = require('mongoose');
var ProjectConverted = mongoose.model('MapConvertedProject');
var map_converted = new ProjectConverted();
//map_converted._id is undefined
//I tried adding the comment below to create an _id manually, but it didn't make a difference when I tried to save
//map_converted._id = mongoose.Types.ObjectId();
console.log("Project Converted ID: " + map_converted._id);
//... fill out the other properties on the schema
map_converted.save(function(err)
{
if(err)
{
//...
}
});
The save generates this error:
ValidationException: One or more parameter values were invalid: Missing the key _id in the item
Does anyone know what is causing this?
I figured this out. There was another place in the code that had a dynamoose model with the same name that was messing things up. I was able to remove all references to dynamoose since it doesn't appear to be used anymore and that cleared up this issue.
Using node.js, mongodb on mongoHQ and mongoose. I'm setting a schema for Categories. I would like to use the document ObjectId as my categoryId.
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
categoryId : ObjectId,
title : String,
sortIndex : String
});
I then run
var Category = mongoose.model('Schema_Category');
var category = new Category();
category.title = "Bicycles";
category.sortIndex = "3";
category.save(function(err) {
if (err) { throw err; }
console.log('saved');
mongoose.disconnect();
});
Notice that I don't provide a value for categoryId. I assumed mongoose will use the schema to generate it but the document has the usual "_id" and not "categoryId". What am I doing wrong?
Unlike traditional RBDMs, mongoDB doesn't allow you to define any random field as the primary key, the _id field MUST exist for all standard documents.
For this reason, it doesn't make sense to create a separate uuid field.
In mongoose, the ObjectId type is used not to create a new uuid, rather it is mostly used to reference other documents.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Product = new Schema({
categoryId : ObjectId, // a product references a category _id with type ObjectId
title : String,
price : Number
});
As you can see, it wouldn't make much sense to populate categoryId with a ObjectId.
However, if you do want a nicely named uuid field, mongoose provides virtual properties that allow you to proxy (reference) a field.
Check it out:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
title : String,
sortIndex : String
});
Schema_Category.virtual('categoryId').get(function() {
return this._id;
});
So now, whenever you call category.categoryId, mongoose just returns the _id instead.
You can also create a "set" method so that you can set virtual properties, check out this link
for more info
I was looking for a different answer for the question title, so maybe other people will be too.
To set type as an ObjectId (so you may reference author as the author of book, for example), you may do like:
const Book = mongoose.model('Book', {
author: {
type: mongoose.Schema.Types.ObjectId, // here you set the author ID
// from the Author colection,
// so you can reference it
required: true
},
title: {
type: String,
required: true
}
});
My solution on using ObjectId
// usermodel.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
UserSchema.set('autoIndex', true)
module.exports = mongoose.model('User', UserSchema)
Using mongoose's populate method
// controller.js
const mongoose = require('mongoose')
const User = require('./usermodel.js')
let query = User.findOne({ name: "Person" })
query.exec((err, user) => {
if (err) {
console.log(err)
}
user.events = events
// user.events is now an array of events
})
The solution provided by #dex worked for me. But I want to add something else that also worked for me: Use
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
if what you want to create is an Array reference. But if what you want is an Object reference, which is what I think you might be looking for anyway, remove the brackets from the value prop, like this:
let UserSchema = new Schema({
username: {
type: String
},
events: {
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}
})
Look at the 2 snippets well. In the second case, the value prop of key events does not have brackets over the object def.
You can directly define the ObjectId
var Schema = new mongoose.Schema({
categoryId : mongoose.Schema.Types.ObjectId,
title : String,
sortIndex : String
})
Note: You need to import the mongoose module
Another possible way is to transform your _id to something you like.
Here's an example with a Page-Document that I implemented for a project:
interface PageAttrs {
label: string
// ...
}
const pageSchema = new mongoose.Schema<PageDoc>(
{
label: {
type: String,
required: true
}
// ...
},
{
toJSON: {
transform(doc, ret) {
// modify ret directly
ret.id = ret._id
delete ret._id
}
}
}
)
pageSchema.statics.build = (attrs: PageAttrs) => {
return new Page({
label: attrs.label,
// ...
})
}
const Page = mongoose.model<PageDoc, PageModel>('Page', pageSchema)
Now you can directly access the property 'id', e.g. in a unit test like so:
it('implements optimistic concurrency', async () => {
const page = Page.build({
label: 'Root Page'
// ...
})
await page.save()
const firstInstance = await Page.findById(page.id)
const secondInstance = await Page.findById(page.id)
firstInstance!.set({ label: 'Main Page' })
secondInstance!.set({ label: 'Home Page' })
await firstInstance!.save()
try {
await secondInstance!.save()
} catch (err) {
console.error('Error:', err)
return
}
throw new Error('Should not reach this point')
})