How to use mongoose database Model as variable inside a class - node.js

What I'm trying to do is access the mongoose database Model (to perform operations in the database) inside a class as a static variable. I tried doing this, but when I run the server there's an error:
{"error":"TypeError: Device.db.find is not a function"}
This is the code from mongoosemodel.js:
import mongoose from 'mongoose';
const deviceSchema = new mongoose.Schema({
id: String,
name: String,
createdAt: Number,
updatedAt: Number
});
const DeviceModel = mongoose.model("Device", deviceSchema); //devices is the collection name
export default DeviceModel;
And on the class:
import mUUID from 'uuid-mongodb'
import mongoose from 'mongoose'
import database from '../mongoosemodel.js';
export default class Device {
static db = database
constructor (data = {}) {
const { id = mUUID.v4().toString(), name, createdAt, updatedAt } = data
this.id = id
this.name = name
this.createdAt = createdAt
this.updatedAt = updatedAt
}
static async getAll () {
const devices = await Device.db.find();
return devices.map(data => new Device(data))
}
static async findById (id) {
const devices = await Device.db.find();
const data = devices.find(d => d.id === id)
if (data) return new Device(data)
}
Maybe I'm doing something wrong with the imports/exports.

Related

Unable to save nested data in MongoDB using NestJs and Mongoose

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

How to use objectId validation in joiful?

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
}

Mongoose - multiple database connections

I want to understand how to switch between databases within mongoose global promise connection.
My current connection is established this way app.ts
import * as mongoose from 'mongoose';
...
try {
await mongoose.createConnection(`mongodb://localhost:27017/db1`, {
useNewUrlParser: true,
})
console.log("Connected")
} catch (error) {
console.log(error)
}
And then I am accessing it in different files some.model.ts
import { Schema, Document, model } from 'mongoose';
const SomeSchema: Schema = new Schema({
name: { type: String, required: true },
owner: { type: string, required: true }
});
export default model('Some', SomeSchema);
According to documentation.
So far we've seen how to connect to MongoDB using Mongoose's default connection. At times we may need multiple connections open to Mongo, each with different read/write settings, or maybe just to different databases for example. In these cases we can utilize mongoose.createConnection() which accepts all the arguments already discussed and returns a fresh connection for you.
const conn = mongoose.createConnection('mongodb://[username:password#]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);
I can create multiple database connections like this
try {
const db1 = await mongoose.createConnection(`mongodb://localhost:27017/db1`, {
useNewUrlParser: true,
})
const db2 = await mongoose.createConnection(`mongodb://localhost:27017/db2`, {
useNewUrlParser: true,
})
console.log("Connected")
} catch (error) {
console.log(error)
}
I can see both connection in console.log(mongoose.connections)
But how can I specify what database should be used for the Model in some.model.ts?
import { Schema, Document, model } from 'mongoose';
const SomeSchema: Schema = new Schema({
name: { type: String, required: true },
owner: { type: string, required: true }
});
export default SPECIFY_DATABASE.model('Some', SomeSchema);
I have found other questions like this but there are connections created "localy", I need to use mongoose connection across many different files.
Thank you for answers, if you need more explanation please let me now.
You need to actually return the connection, and then register a given model to each of the connections. To clarify, you need:
something to create a (named, specific) connection
schemas
you create models by registering schemas to the given connections,
you also need something to orchestrate it.
Example, lets have a "db.js" file (I call mine "repo.js" usually) with a single export, a function that returns the initialized database Promise.
You'd use it by importing the function and awaiting for the db.
I have a bit of a longer example, so error handling etc is ommited for brevity.
import { createConnections } from './create-connections';
import { UsersSchema } from './users-schema';
import { PostsSchema } from './posts-schema';
let db: any;
export function getDatabase(): Promise<any> {
if (this.db) return Promise.resolve(db);
return createDatabases();
}
async function createDatabases() {
const { db1, db2 } = await createConnections('mongodb://localhost/db1', 'mongodb://localhost/db2');
const UserModel = db1.model('users', UserSchema);
const PostModel = db2.model('posts', PostSchema);
db = {
UserModel,
PostModel,
// also if you need this
connections: {
db1,
db2,
}
}
return db;
}
Now, I've used './create-connections' here, which is almost what you have:
// create-connection.js
const { createConnection } = require('mongoose');
// You create connections by calling this function and giving it the URL to the server
export function createConnections(url1, url2) {
const db1 = await createConnection(url1);
const db2 = await createConnection(url2);
return {
db1,
db2
}
}
Now, let's say you have two models: users and posts, let's have their schemas.
// users schema
import { Schema, Document } from 'mongoose';
export const UserSchema: Schema = new Schema({
name: { type: String, required: true },
});
// posts schema
import { Schema, Document } from 'mongoose';
export const PostSchema: Schema = new Schema({
text: { type: String, required: true },
owner: { type: SchemaID, required: true }
});
So now you need to bind it all in that fdirst file.
But how to use it? As I've said, since it's async, you always import it and use it as a simple async getDB:
// some controller, route handler, service etc.
import { getDatabase } from './get-database';
router.get('/users', async (req, res) => {
const User = await getDatabase().UserModel;
const users = await User.find();
return res.json(users);
});
router.post('/posts', async (req, res) {
const { text } = req.body;
const owner = req.user.id;
const Post = await getDatabase().PostModel;
const post = await Post.create({ text, owner });
return res.json(post);
});

Mongoose: handling multiple databases when working with one model

What I want is to have arbitrary databases (50 for example) with the same collections (same schemas, exact models, different data) and 1 nodejs (expressjs + mongoose) web app.
Example simplified case:
I have:
a single web application (expressjs + mongoose) with User model.
50 domains 50 databases with users collection.
What behaviour I want to achieve:
GET /api/users/ http request is coming to one of domains (test-domain-39.myapp.com)
app gets the requested domain name (test-domain-39) and somehow mongoose understands that it wants to query database-39 when I just do User.find({isActive: true}) in users.controller
So I just want an abstraction. I pass db name to mongoose and continue to work with the User model (as we all usually do when having single DB connection) and mongoose, if needed, creates connection (if it's the first request to the specific db), keeps it alive for next requests in connection pool and etc.
What's the most simple and efficient way to accomplish that?
Thank's in advance!
IMHO, while this is possible with MongoDB, I wouldn't advise maintaining a separate database for each domain, especially if you are expecting to have a huge number of them. Have you considered a multi-tenant model instead?
The sample code below adds user 'Alex' into two different databases, "domainOne" and "domainTwo". Hope this helps
var mongoose = require('mongoose');
var personSchema = { name: String, domain : String };
var baseUri = 'mongodb://localhost/';
domains.forEach((domain) => {
var conn = mongoose.createConnection(baseUri + domain, (error) => {
if(error){
console.log('Ups! Database connection failed!');
return;
}
//Use the connection object to create your models,
//instead the mongoose object
//so that our data is saved into the database
//associated with this connection
var Person = conn.model('Person', personSchema);
//Lets add user 'Alex' into the database
(new Person({name : 'Alex', domain : domain })).save((error) => {
if(error){
console.log('Ups! Could not save person');
} else {
conn.close();
}
});
});
});
This is how I implemented my project:
// config/db.ts
import {createConnection} from 'mongoose'
const MONGO_URI = process.env.MONGO_URI
if (!MONGO_URI)
throw new Error(
'Please define the MONGO_URI environment variable inside .env'
)
const connections: any = {}
async function db(dbName: string) {
if (connections[dbName]) {
return connections[dbName]
} else {
connections[dbName] = createConnection(`${MONGO_URI}/${dbName}`)
return connections[dbName]
}
}
export default db
// models/Test.ts
import { Schema } from 'mongoose'
export interface ITest {
_id: Schema.Types.ObjectId
name: string
createdAt?: Date
}
const testSchema = new Schema<ITest>(
{
name: { type: String, required: true },
},
{ timestamps: true }
)
export default testSchema
// pages/api/test.ts
import nc from 'next-connect'
import db from '../../config/db'
import testSchema from '../../models/Test'
const handler = nc()
handler.get(
async (req: NextApiRequestExtended, res: NextApiResponseExtended) => {
try {
const conn = await db(req.headers['x-db-key'])
const model = await conn.model('Test', testSchema)
const data = await model.find({})
res.send(data)
} catch (error: any) {
res.status(500).json({ error: error.message })
}
}
)
export default handler

nodejs mongo prototype best practices

I'm new in the node.js world, but I'm trying to do a REST API with mongoDB and some javascript prototyping.
What is the best approach to have a model and the prototype object? Do i need to have the mongo schema definition in the same class of the prototype?
For example:
var Person = function (name) {
this.name = name;
}
Person.prototype.getSchema = function () { //To-do create mongo schema
}
Person.prototype.getName = function () {
return this.name;
}
Is that a good approach? Do I have to modify something?
I recommend to you starting with mongoose.
In mongoose would be something like this:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
var userSchema = new Schema({
username: String,
password: String
})
userSchema.statics = {
getByName(name) {
return this.find({name})
.exec(function(err, user) {
console.log(user);
});
}
}
module.exports = mongoose.model('User', userSchema)
Then in your controller you can import the User model and use the model method.

Resources