About Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js https://mongoosejs.com/docs/index.html npm install mongoose --save Config // connectToDb.ts import mongoose from 'mongoose' const mongoDbUrl = process.env.MONGO_DB_CONNECTION_STRING const db = 'q' export const connectToDb = async (): Promise<void> => { try { if (!mongoDbUrl) return mongoose.set('strictQuery', false) await mongoose.connect(`${mongoDbUrl}/${db}`) console.info('🚀 connected to db') } catch (error) { console.warn('💣 error on db connection') console.error(error) } } import 'dotenv/config' import express from 'express' import { connectToDb } from './db/connectToDb' import { errorHandlerMiddleware } from './middleware/errorHandlerMiddleware' import { multerMiddleware } from './middleware/multerMiddleware' import type { Req, Res } from './types' const app = express() void connectToDb() app.use(morgan('dev')) // http logs in terminal app.use(express.json()) // parses incoming requests with JSON because we use lots of json, let it be default app.use(cookieParser()) app.use(cors()) app.use(multerMiddleware.single('file')) app.set('trust proxy', true) // for app engine app.get(apiUrl.root, (_req: Req, res: Res) => res.send('i am express.js') app.use(errorHandlerMiddleware) const port = process.env.PORT_BACK_END const domain = process.env.DOMAIN // const tellServerStarted = (): void => { console.info(`🚀 server started at ${domain}:${port}`) } app.listen(port, tellServerStarted) schema and model // userModel.ts // import type { HydratedDocumentFromSchema, InferSchemaType } from 'mongoose' import { model, Schema } from 'mongoose' // define schema for documents in collection const userSchema = new Schema({ email: { type: String, required: [true, 'email is required'], unique: true, trim: true, match: /.+@.+..+/, }, password: { type: String, required: [true, 'password is required'], trim: true, }, roles: { type: [String], default: ['user'], }, isActivated: { type: Boolean, default: false, }, activationLink: { type: String, }, refreshJwtToken: { type: String, }, }, { timestamps: true, }) // define model - a class with which we construct documents // each document is a user with props as in schema // * "users" collection will be created automatically based on this model export const UserModel = model('user', userSchema) // Type of an hydrated document (with all the getters, etc...) // export type HydratedUserModel = HydratedDocumentFromSchema<typeof UserSchema> // Only the fields defined in the schema // export type UserModelProps = InferSchemaType<typeof UserSchema> // quotationModel.ts import { model, type ObjectId, Schema } from 'mongoose' import { customAlphabet } from 'nanoid' const nanoid = customAlphabet('123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ') export type QuotationModelType = { _id: ObjectId id: string email: string quotationName?: string createdAt: Date updatedAt: Date sharedAt?: Date from?: { email: string name: string company: string } to?: { email: string name: string company: string } version: number } const quotationSchema = new Schema<QuotationModelType>({ id: { type: String, default: () => nanoid(5), required: true, unique: true, index: true, }, email: { type: String, required: true, index: true, lowercase: true, trim: true, }, quotationName: { type: String, trim: true, }, createdAt: Date, updatedAt: Date, sharedAt: Date, from: { email: String, name: String, company: String, }, to: { email: String, name: String, company: String, }, version: { type: Number, default: 1, validate: (version: number) => version >= 0, }, }, { timestamps: true, }) export const QuotationModel = model<QuotationModelType>('quotation', quotationSchema) Create document // create document const dbRes = await QuotationModel.create({ email: 'mail@mail.com' }) // create document with save const doc = new QuotationModel({ email: 'new@email.com' }) const dbRes = await doc.save() // create multiple document at once const dbRes = await QuotationModel.create([ { email: '1@mail.com' }, { email: '2@mail.com' }, { email: '3@mail.com' }, ]) // same, but faster, but has some drawback, which I did not get const dbRes = await QuotationModel.insertMany([ { email: '10@mail.com' }, { email: '20@mail.com' }, { email: '30@mail.com' }, ]) const dbRes = await QuotationModel.find() res.json({ dbRes }) Get all documents // get all documents const dbRes = await QuotationModel.find() select // do not show internal props _id __v const dbRes = await QuotationModel.find().select({ _id: 0, __v: 0 }) // show only name & email const dbRes = await QuotationModel.find({}, 'name email') const dbRes = await QuotationModel.find().select({ name: 1, email: 1 }) // same, but more readable, find docs with email, select 'name' + 'email' - '_id' fields, then execute the query const query = QuotationModel.find({ email: 'anton.arbus@gmail.com' }) void query.select('name email -_id') const dbRes = await query.exec() find // docs with exact email value const dbRes = await UserModel.find({ email: 'anton.arbus@gmail.com' }) // empty array is returned if no docs found const dbRes = await UserModel.find({ email: 'non existing email' }) // may use RegExp const dbRes = await UserModel.find({ email: /^anton/ }) // RegExp, same as above const dbRes = await UserModel.find({ email: { $regex: '^anton' } }) // queries inside find const dbRes = await QuotationModel .find({ email: /gmail/, id: /j/i, age: { $gt: 17, $lt: 66 }, likes: { $in: ['vaporizing', 'talking'] }, }) .select({ id: true, email: 1, url: 1 }) // queries inside find const dbRes = await QuotationModel .find({ email: /gmail/, id: /j/i, age: { $gt: 17, $lt: 66 }, likes: { $in: ['vaporizing', 'talking'] }, }) .select({ id: true, email: 1, url: 1 }) limit const dbRes = await QuotationModel.find().limit(2) sort const dbRes = await QuotationModel.find().sort({ id: -1, url: 1 }) findOne // first found document const dbRes = await QuotationModel.findOne() // first document with email const dbRes = await QuotationModel.findOne({ email: 'anton.arbus@gmail.com' }) where // where const dbRes = await QuotationModel.find({ email: 'anton.arbus@gmail.com' }).where({ id: 'pEBgU' }) // queries with where const dbRes = await QuotationModel .find({ email: /gmail/ }) .where('id').equals(/j/i) .where('age').gt(17).lt(66) .where('likes').in(['vaporizing', 'talking']) .limit(2) .sort('+id') .select('id email url') .exec() findById // find by id const dbRes = await QuotationModel.findById('65dd14c495adae57a02a34ed') findOneAndUpdate // find & update, return not updated doc const filter = { email: 'anton.arbus@gmail.com', id: 'X4vjR' } const update = { url: 'updated url' } const dbRes = await QuotationModel.findOneAndUpdate(filter, update, { returnOriginal: true, }) // find & update, return updated doc const filter = { email: 'anton.arbus@gmail.com', id: 'X4vjR' } const update = { url: 'brand new url' } const dbRes = await QuotationModel.findOneAndUpdate(filter, update, { returnOriginal: false, // new: true // same thing as returnOriginal: false }) findOne, modify, save // find and update with save const document = await QuotationModel.findOne({ email: 'anton.arbus@gmail.com' }).where({ id: 'pAx6q' }) if (document !== null) { document.url = 'new url' const dbRes = await document.save() res.json({ dbRes }) } findOneAndUpdate or insert // find & update, if not found --> insert const filter = { email: 'anton.the.best@gmail.com' } const update = { quotationName: 'i am quotation' } const dbRes = await QuotationModel.findOneAndUpdate(filter, update, { new: true, setDefaultsOnInsert: true, upsert: true, includeResultMetadata: true, }) updateOne // updateOne const dbRes = await QuotationModel.updateOne({ email: 'anton.arbus@gmail.com' }, { email: 'arbus.anton@gmail.com' }) updateMany // update all const dbRes = await QuotationModel.updateMany({}, { version: 1 }) // update version where email === 'anton.arbus@gmail.com' const dbRes = await QuotationModel.updateMany({ email: 'anton.arbus@gmail.com' }, { version: 2 }) // update many documents const dbRes = await QuotationModel.updateMany({ email: 'anton.arbus@gmail.com' }, { $set: { url: 'some url' } }) deleteOne // delete const dbRes = await QuotationModel.deleteOne({ email: 'anton.arbus@gmail.com' }).where({ id: 'pEBgU' }) deleteMany // delete all docs where url === 'some url' const dbRes = await QuotationModel.deleteMany({ url: 'some url' }) lean // return lean result, 5x smaller size, better for server // it does not support 1. change tracking 2. validation 3. getters and setters 4. virtuals 5. save() const dbRes = await QuotationModel.find().lean() countDocuments // count found documents const dbRes = await QuotationModel.find().countDocuments() // same, but faster const dbRes = await QuotationModel.find().estimatedDocumentCount() exists // returns doc id if it is found or null const dbRes = await QuotationModel.exists({ email: 'anton.arbus@gmail.comm' }) validate // validate const document = await QuotationModel.findOne() if (document !== null) { // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression const dbRes = await document.validate() res.json({ dbRes }) } explain // explain - stats about how it executed a query const dbRes = await QuotationModel.find().explain() populate // The populate method is used to replace specified paths in a document // with actual document(s) from another collection. // This is useful when you have references between documents in different collections and // you want to retrieve the referenced documents along with the original document in a single query. const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ username: String, email: String }); const postSchema = new mongoose.Schema({ title: String, content: String, author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }); const User = mongoose.model('User', userSchema); const Post = mongoose.model('Post', postSchema); Post.findOne({ title: 'Example Post' }) .populate('author') .exec((err, post) => { if (err) throw err; console.log(post); }); operators // Comparison Operators: const resEq = await Model.find({ field: { $eq: value } }); // Equal to a specified value const resNe = await Model.find({ field: { $ne: value } }); // Not equal to a specified value const resGt = await Model.find({ field: { $gt: value } }); // Greater than a specified value const resLt = await Model.find({ field: { $lt: value } }); // Less than a specified value const resGte = await Model.find({ field: { $gte: value } }); // Greater than or equal to a specified value const resLte = await Model.find({ field: { $lte: value } }); // Less than or equal to a specified value const resIn = await Model.find({ field: { $in: [value1, value2] } }); // Matches any value in an array const resNin = await Model.find({ field: { $nin: [value1, value2] } }); // Does not match any value in an array // Logical Operators: const resAnd = await Model.find({ $and: [ { condition1 }, { condition2 } ] }); // Logical AND const resOr = await Model.find({ $or: [ { condition1 }, { condition2 } ] }); // Logical OR const resNot = await Model.find({ field: { $not: { $eq: value } } }); // Inverted query expression // Element Operators: const resExists = await Model.find({ field: { $exists: true } }); // Field exists const resType = await Model.find({ field: { $type: 'string' } }); // Field is of specified type // Array Operators: const resAll = await Model.find({ field: { $all: [value1, value2] } }); // Matches arrays with all specified elements const resElemMatch = await Model.find({ field: { $elemMatch: { subfield: value } } }); // Matches arrays with at least one element meeting criteria // Regular Expression Operators: const resRegex = await Model.find({ field: { $regex: /pattern/ } }); // Matches using a regular expression // Geospatial Operators: const resGeoWithin = await Model.find({ location: { $geoWithin: { $geometry: { type: "Polygon", coordinates: [...] } } } }); // Within a specified shape const resGeoIntersects = await Model.find({ location: { $geoIntersects: { $geometry: { type: "Point", coordinates: [x, y] } } } }); // Intersects with a specified point // Text Search Operators: const resTextSearch = await Model.find({ $text: { $search: 'keyword' } }); // Text search for a keyword // Miscellaneous Operators: const resMod = await Model.find({ field: { $mod: [divisor, remainder] } }); // Modulo operation on field value const resWhere = await Model.find({ $where: 'this.field === value' }); // JavaScript expression-based query methods for chaining query operators // Comparison Operators: const resEq = await Model.find().where('field').equals(value); // Equal to a specified value const resNe = await Model.find().where('field').ne(value); // Not equal to a specified value const resGt = await Model.find().where('field').gt(value); // Greater than a specified value const resLt = await Model.find().where('field').lt(value); // Less than a specified value const resGte = await Model.find().where('field').gte(value); // Greater than or equal to a specified value const resLte = await Model.find().where('field').lte(value); // Less than or equal to a specified value const resIn = await Model.find().where('field').in([value1, value2]); // Matches any value in an array const resNin = await Model.find().where('field').nin([value1, value2]); // Does not match any value in an array // Logical Operators: const resAnd = await Model.find().and([ { condition1 }, { condition2 } ]); // Logical AND const resOr = await Model.find().or([ { condition1 }, { condition2 } ]); // Logical OR const resNot = await Model.find().where('field').not().equals(value); // Inverted query expression // Element Operators: const resExists = await Model.find().where('field').exists(); // Field exists const resType = await Model.find().where('field').type('string'); // Field is of specified type // Array Operators: const resAll = await Model.find().where('field').all([value1, value2]); // Matches arrays with all specified elements const resElemMatch = await Model.find().where('field').elemMatch({ subfield: value }); // Matches arrays with at least one element meeting criteria // Regular Expression Operators: const resRegex = await Model.find().where('field').regex(/pattern/); // Matches using a regular expression // Geospatial Operators: const resGeoWithin = await Model.find().where('location').geoWithin({ $geometry: { type: "Polygon", coordinates: [...] } }); // Within a specified shape const resGeoIntersects = await Model.find().where('location').geoIntersects({ $geometry: { type: "Point", coordinates: [x, y] } }); // Intersects with a specified point // Text Search Operators: const resTextSearch = await Model.find().text('keyword'); // Text search for a keyword // Miscellaneous Operators: const resMod = await Model.find().where('field').mod(divisor, remainder); // Modulo operation on field value const resWhere = await Model.find().where('field').equals(value); // JavaScript expression-based query