Mongoose typing issue with typescript - node.js

I am building an app using mongoose and typescript. Here is a simple model I have made:
import * as callbackMongoose from 'mongoose';
var mongoose = callbackMongoose;
mongoose.Promise = global.Promise;
const Schema = mongoose.Schema;
var userSchema = new Schema({
username: String,
email: String,
hash: String
});
export default mongoose.model('User', userSchema);
It works well but I need to cast each document to any before accessing properties. I read a guide that said I could do this:
interface IUser extends mongoose.Document {
username: String;
email: String;
hash: String;
}
export default mongoose.model<IUser>('User', userSchema);
My problem is that the type mongoose doesn't seem to have the property Document. It also doesn't have the property ObjectId. When I cast mongoose to any and use these members it works just fine. It seems to be a typing issue.
I installed the mongoose typing like so:
npm install #types/mongoose --save
The typings do work for Schema and they are good for all of the other libraries I use. Is something wrong with these type definitions? Am I doing something wrong?

For TypeScript#2.0
I think you may use
npm install #types/mongoose --save
instead of:
npm install #typings/mongoose --save
This is full example:
Database.ts
import mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://admin:123456#ds149437.mlab.com:49437/samples');
export { mongoose };
UserData.ts
import { mongoose } from './../../Services/Database';
export interface UserData {
is_temporary: boolean;
is_verified: boolean;
status: boolean;
username: string;
}
export interface IUserData extends UserData, mongoose.Document, mongoose.PassportLocalDocument { };
UserModel.ts
import { IUserData } from './UserData';
import { mongoose } from './../../Services/Database';
import * as passportLocalMongoose from 'passport-local-mongoose';
import Schema = mongoose.Schema;
const UserSchema = new Schema({
username: { type: String, required: true },
password: String,
status: { type: Boolean, required: true },
is_verified: { type: Boolean, required: true },
is_temporary: { type: Boolean, required: true }
});
UserSchema.plugin(passportLocalMongoose);
var UserModel;
try {
// Throws an error if 'Name' hasn't been registered
UserModel = mongoose.model('User')
} catch (e) {
UserModel = mongoose.model<IUserData>('User', UserSchema);
}
export = UserModel;
I also full project example using typescript, node.js, mongoose & passport.js right here: https://github.com/thanhtruong0315/typescript-express-passportjs
Good luck.

Related

How to insert data in mongodb using mongoose in typescript?

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

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.

ES6: Module '"mongoose"' has no default export

Using ES6 Imports & Exports I feel like I should be able to declare the import as
import mongoose, { Schema, Document } from 'mongoose'; but I get the error Module '"mongoose"' has no default export.
The below does work but it clearly isn't the right way to go about this import. The export default id like to remove too.
import * as mongoose from 'mongoose';
import { Schema, Document } from 'mongoose';
export interface IUser extends Document {
email: string;
password: string;
}
const UserSchema: Schema = new Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
});
export default mongoose.model<IUser>('User', UserSchema);
And then im using it with
import UserModel, { IUser } from '../models/example'
import * as bcrypt from 'bcrypt';
class User {
static register = async (req: Request, res: Response) => {
const email = req.body.email;
const password = req.body.password;
const alreadyRegistered = await UserModel.findOne({email}).exec();
if (!alreadyRegistered) {
const hashedPassword = await bcrypt.hash(password,10);
if (!hashedPassword) {
res.status(500).send({ message: "Failed to encrypt your password" });
} else {
const user = new UserModel(<IUser>{email:email, password:hashedPassword});
const saved = await user.save();
if (!saved) {
res.status(500).send({ message: "Failed to register you" });
} else {
res.status(200).send({ message: "You are now registered" });
}
}
} else {
res.status(400).send({ message: "You have already registered" });
}
};
}
export {User}
I came across this issue and fixed it by adding the following to tsconfig.json
"esModuleInterop": true
Then I could import as normal
import mongoose from 'mongoose'
Try This
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
});
module.exports = mongoose.model('User', UserSchema);
2nd Way -
import * as mongoose from 'mongoose';
type UserSchema = IUser & mongoose.Document;
const User = mongoose.model<UserSchema>('User', new mongoose.Schema({
email : { type: String, required: true, unique: true },
password: { type: String, required: true }
}));
export = User;
Make sure that you have "allowSyntheticDefaultImports": true, in tsconfig.json file.
Reference: https://github.com/microsoft/TypeScript-React-Starter/issues/8#issuecomment-301265017
I am not sure how you are compiling the typescript but it should work just fine with the defaults set in tsconfig.json file.
generate tsconfig.json in your project's root directory by running tsc --init (as mentioned, you can use defaults for this to work with target set even to es5)
now you can run tsc -w or ts-node index.ts or however you prefer to build/run your code
Note that the index.ts file must be either in your project's root or you will need to specify the path in the tsconfig.json (the rootDir options which is commented out by default and uses path relative to the tsconfig file).
Also note that if you are running tsc -w or some other command that compiles/runs the code from some other directory than the one where tsconfig.json is located, you will need to provide a path to it, i.e. tsc -p path_to_config -w
I Have the same issue I noticed that I missed the tsconfig.json
import { model, Schema } from 'mongoose';
...
export default model<IUser>('User', UserSchema);

Creating mongoose models with typescript - subdocuments

I am in the process of implementing mongoose models with typescript as outlined in this article: https://github.com/Appsilon/styleguide/wiki/mongoose-typescript-models and am not sure of how this translates when you are working with arrays of subdocuments. Let's say I have the following model and schema definitions:
interface IPet {
name: {type: mongoose.Types.String, required: true},
type: {type: mongoose.Types.String, required: true}
}
export = IPet
interface IUser {
email: string;
password: string;
displayName: string;
pets: mongoose.Types.DocumentArray<IPetModel>
};
export = IUser;
import mongoose = require("mongoose");
import IUser = require("../../shared/Users/IUser");
interface IUserModel extends IUser, mongoose.Document { }
import mongoose = require("mongoose");
import IPet = require("../../shared/Pets/IPet");
interface IPetModel extends IPet, Subdocument { }
code that would add a new pet to the user.pet subdocument:
addNewPet = (userId: string, newPet: IPet){
var _user = mongoose.model<IUserModel>("User", userSchema);
let userModel: IUserModel = await this._user.findById(userId);
let pet: IPetModel = userModel.pets.create(newPet);
let savedUser: IUser = await pet.save();
}
After reviewing the link, this seems to be the ideal approach necessary for handling subdocuments. However, this scenario seems to result in a CasterConstructor exception being thrown:
TypeError: Cannot read property 'casterConstructor' of undefined at Array.create.
Is it the right approach to dealing with Subdocuments when using mongoose models as outlined in the linked article above?
you can try this package https://www.npmjs.com/package/mongoose-ts-ua
#setSchema()
class User1 extends User {
#prop()
name?: string;
#setMethod
method1() {
console.log('method1, user1');
}
}
#setSchema()
class User2 extends User {
#prop({ required: true })
name?: string;
#prop()
child: User1;
}
export const User2Model = getModelForClass<User2, typeof User2>(User2);
usage
let u2 = new User2Model({ child: { name: 'u1' } });

Typescript: static methods for mongoose schemas

I'm trying to implement a mongoose model with TypeScript, nothing fancy, just try to make it work. This code compiles but with warnings:
import crypto = require('crypto')
import mongoose = require('mongoose')
mongoose.Promise = require('bluebird')
import {Schema} from 'mongoose'
const UserSchema = new Schema({
name: String,
email: {
type: String,
lowercase: true,
required: true
},
role: {
type: String,
default: 'user'
},
password: {
type: String,
required: true
},
provider: String,
salt: String
});
/**
* Methods
*/
UserSchema.methods = {
// my static methods... like makeSalt, etc
};
export default mongoose.model('User', UserSchema);
But typescript is complaining:
error TS2339: Property 'methods' does not exist on type 'Schema'.
I presume that i need to extend some interface. Any pointer with this?
The Schema typing doesn't allow for extension by default. In typescript interfaces are open and are extensible. You would need to extend the typings for Schema to include the fields you are expanding, otherwise typescript doesn't know about it. This is a good answer for extending a type. How do you explicitly set a new property on `window` in TypeScript?
If you look at https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/mongoose/mongoose.d.ts, you will see the general typings for mongoose.
What you are most likely looking to do is the following, though I don't know if you can extend classes:
schema-extended.d.ts
module "mongoose" {
export class Schema {
methods:any
}
}
Then in your code:
///<reference path="./schema-extended.d.ts" />
//Schema should now have methods as a property.
new Schema().methods

Resources