【MongoDB】简单入门/与Express结合设计api

简单介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

简单使用

我的运行命令: mongod --dbpath "D:\Program Files\MongoDB\Server\6.0\data\db"
创建和插入数据
insertOne插入一个数据,insertMany插入多个
db代表当前的库

// 新建一个库
> use natours-test
//插入
natours-test> db.tours.insertOne({name: "The Forest Hiker",price:297,rating: 4.7})
- 返回:
{
  acknowledged: true,
  insertedId: ObjectId("63f384f60b988fe7c3caf7e6")
}
//使用find返回创建的对象
natours-test> db.tours.find()
- 返回:
[
  {
    _id: ObjectId("63f384f60b988fe7c3caf7e6"),
    name: 'The Forest Hiker',
    price: 297,
    rating: 4.7
  }
]
//显示所有的
natours-test> show dbs

admin          40.00 KiB
config        108.00 KiB
local          72.00 KiB
natours-test   40.00 KiB

//会显示所有创建的文档
natours-test> show collections
返回
tours

CRUD操作

插入

插入多个,且不同结构
natours-test> db.tours.insertMany([{name:"The Sea Explorer",price: 497,rating:4.8},{name:"The Show Adventurer",price : 997,rating:4.9,difficulty: "easy"}])

natours-test> db.tours.find()
[
  {
    _id: ObjectId("63f384f60b988fe7c3caf7e6"),
    name: 'The Forest Hiker',
    price: 297,
    rating: 4.7
  },
  {
    _id: ObjectId("63f38d8c14709b1a5cdb255f"),
    name: 'The Sea Explorer',
    price: 497,
    rating: 4.8
  },
  {
    _id: ObjectId("63f38d8c14709b1a5cdb2560"),
    name: 'The Show Adventurer',
    price: 997,
    rating: 4.9,
    difficulty: 'easy'
  }
]

查询

mongodb works with object

  • find()
    传入对象进行搜索
natours-test> db.tours.find({name: "The Forest Hiker"})
[
  {
    _id: ObjectId("63f384f60b988fe7c3caf7e6"),
    name: 'The Forest Hiker',
    price: 297,
    rating: 4.7
  }
]

传入一个新对象{$lte:500}—>小于运算符less then or echo
lt:less then
gte:greater than or echo
or:{$or: [{price: {$lt: 500}},{rating:{$gte:4.8}}]}
投影:筛选出特定需要展示的元素,设置为1


natours-test> db.tours.find({price:{$lte:500}})
[
  {
    _id: ObjectId("63f384f60b988fe7c3caf7e6"),
    name: 'The Forest Hiker',
    price: 297,
    rating: 4.7
  },
  {
    _id: ObjectId("63f38d8c14709b1a5cdb255f"),
    name: 'The Sea Explorer',
    price: 497,
    rating: 4.8
  }
]
两个条件的查询
natours-test> db.tours.find({price:{$lt:500},rating:{$gte:4.8}})
----
[
  {
    _id: ObjectId("63f38d8c14709b1a5cdb255f"),
    name: 'The Sea Explorer',
    price: 497,
    rating: 4.8
  }
]

或运算:
natours-test> db.tours.find({$or: [{price: {$lt: 500}},{rating:{$gte:4.8}}]})
----
[
  {
    _id: ObjectId("63f384f60b988fe7c3caf7e6"),
    name: 'The Forest Hiker',
    price: 297,
    rating: 4.7
  },
  {
    _id: ObjectId("63f38d8c14709b1a5cdb255f"),
    name: 'The Sea Explorer',
    price: 497,
    rating: 4.8
  },
  {
    _id: ObjectId("63f38d8c14709b1a5cdb2560"),
    name: 'The Show Adventurer',
    price: 997,
    rating: 4.9,
    difficulty: 'easy'
  }
]
投影
natours-test> db.tours.find({$or: [{price: {$gt: 500}},{rating:{$gte:4.8}}]},{name:1})
-----------------
[
  {
    _id: ObjectId("63f38d8c14709b1a5cdb255f"),
    name: 'The Sea Explorer'
  },
  {
    _id: ObjectId("63f38d8c14709b1a5cdb2560"),
    name: 'The Show Adventurer'
  }
]

更新

  • db.tours.updateOne(),传入要改的对象和更改的数据,仅修改第一个匹配的数据,使用set进行更新
  • db.tours.replaceOne() ,传入要替换的对象和替换的数据
natours-test> db.tours.updateOne({name:"The Show Adventurer"},{$set: {price:597}})
----------
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
可以用这个创建之前没有的元素,many将修改匹配的多处
natours-test> db.tours.updateMany({price : {$gt:500},rating:{$gte:4.8}},{$set:{premium:true}})

删除

无法撤销!

  • db.tours.deleteMany({rating:{$lt: 4.8} }), 删除符合条件的对象
  • db.tours.deleteMany({ }), 传入空对象以删除所有对象

使用Campass可视化界面进行CRUD

使用Atlas云数据库

创建一个云数据库,然后将密码填入环境变量保存
使用campass进行链接云数据库

连接express

下载 - npm i mongoose@5
配置环境变量时记得在url后面添加自己想要保存的库名:mongodb.net/natours?retryWrites=true

//登录
const DB = process.env.DATABASE.replace(
  '<password>',
  process.env.DATABASE_PASSWORD
);
mongoose
	.connect(process.env.DATABASE_LOCAL, {
    // .connect(DB, {
    useCreateIndex: true,
    useNewUrlParser: true,
    useFindAndModify: false,
     useUnifiedTopology: true,
  })
  .then((con) => {
    console.log(con.connections);
    console.log('DB CONNECTION SUCCESSFUL');
  });

为什么Mongoose

在这里插入图片描述
在这里插入图片描述

使用express和mongoose创建一个文件

创建范式
  • new mongoose.Schema传入两个参数

一个属性可以有多种要求

  • type
  • required:bool&错误提示
  • unique:唯一
  • trim:自动剪裁
  • maxlength,minlength:大小&报错信息
  • enum:可接受的字段
  • default:默认数据
  • select:判断是否需要显示到查询中
  • validate:创建一个函数以自定义数据验证,传入一个对象:validator为函数,msg为错误信息,并且信息可以访问到传入的值:{VALUE}
const tourSchema = new mongoose.Schema({
  name: {
      type: String,
      required: [true, 'A tour must have a name'],
      unique: true,
      trim: true,
      maxlength: [40, 'A tour name must have less or equal then 40 characters'],
      minlength: [10, 'A tour name must have more or equal then 10 characters']
      // validate: [validator.isAlpha, 'Tour name must only contain characters']
    },
    slug: String,
    duration: {
      type: Number,
      required: [true, 'A tour must have a duration']
    },
    maxGroupSize: {
      type: Number,
      required: [true, 'A tour must have a group size']
    },
    difficulty: {
      type: String,
      required: [true, 'A tour must have a difficulty'],
      enum: {
        values: ['easy', 'medium', 'difficult'],
        message: 'Difficulty is either: easy, medium, difficult'
      }
    },
    ratingsAverage: {
      type: Number,
      default: 4.5,
      min: [1, 'Rating must be above 1.0'],
      max: [5, 'Rating must be below 5.0']
    },
    ratingsQuantity: {
      type: Number,
      default: 0
    },
    price: {
      type: Number,
      required: [true, 'A tour must have a price']
    },
    priceDiscount: {
      type: Number,
      validate: {
        validator: function(val) {
          // this only points to current doc on NEW document creation
          return val < this.price;
        },
        message: 'Discount price ({VALUE}) should be below regular price'
      }
    },
    summary: {
      type: String,
      trim: true,
      required: [true, 'A tour must have a description']
    },
    description: {
      type: String,
      trim: true
    },
    imageCover: {
      type: String,
      required: [true, 'A tour must have a cover image']
    },
    images: [String],
    createdAt: {
      type: Date,
      default: Date.now(),
      select: false
    },
    startDates: [Date],
    secretTour: {
      type: Boolean,
      default: false
    }
},
  {
    toJSON: { virtuals: true },
    toObject: { virtuals: true }
  }
  );
const Tour = mongoose.model('Tour', tourSchema);
插入数据

生产一条数据
创建文档然后添加数据

const testTour = new Tour({
  name: 'The Forest Hiker',
  rating: 4.7,
  price: 497,
});

testTour
  .save()
  .then((doc) => {
    console.log(doc);
  })
  .catch((err) => {
    console.log('==========ERROR:=======', err);
  });

建立新数据的方法

  • Tour.create({}) 直接在文档中创建一个数据

MVC架构

在这里插入图片描述
在这里插入图片描述

重构之前的路由:

exports.createTour = async (req, res) => {
  try {
  //使用create方法直接创建对象
    const newTour = await Tour.create(req.body);
	
    res.status(200).json({
      status: 'success',
      data: {
        tour: newTour,
      },
    });
  } catch (err) {
    res.status(400).json({
      status: 'fail to create',
      msg: err,
    });
  }
};

此时在body中的信息如果不是范式中规定的将被忽略
在这里插入图片描述
搜索:

const tour = await Tour.findById(req.params.id);
//Tour.findOne({ _id: req.params.id })

更新:

const tour = await Tour.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
    });

方法大全:https://mongoosejs.com/docs/queries.html
在这里插入图片描述

脚本导入json到数据库

自动跑函数:使用process.argv参数读取

if (process.argv[2] === '--import') {
  importData();
} else if (process.argv[2] === '--delete') {
  deleteData();
}
console.log(process.argv);

命令:node .\import-dev-data.js --import
返回:

[
  'D:\\study\\nodejs\\node.exe',
  'D:\\dev-data\\data\\import-dev-data.js',
  '--import'
]

这样就实现了自动调用
读取文件与删除库


const tours = fs.readFileSync(`${__dirname}/tours-simple.json`, 'utf-8');

const importData = async () => {
  try {
    await Tour.create(JSON.parse(tours));
    console.log('Data successfully loaded!');
    process.exit();
  } catch (err) {
    console.log(err);
  }
};

const deleteData = async () => {
  try {
    await Tour.deleteMany();
    console.log('Data successfully delete!');
    process.exit();
  } catch (err) {
    console.log(err);
  }
}; 

通过url查询

例:/api/v1/tours?duration=5&difficultu=easy
使用:

   console.log(req.query);

控制台:

   { duration: '5' }
GET /api/v1/tours?duration=5 200 67.560 ms - 9558

两种查询方式:find和where

    // const tours = await Tour.find({
    //   duration: '5',
    // });
    const tuors = await Tour.find()
      .where('duration')
      .equals(5)
      .where('difficulty')
      .equals('easy');
实践
    const tours = await Tour.find(req.query);

使用过滤器过滤不需要的参数

    //深拷贝
    const queryObj = { ...req.query };
    const excludeFields = ['page', 'sort', 'limit', 'fields'];

    excludeFields.forEach((el) => delete queryObj[el]);

    console.log(req.query, queryObj);

    const tours = await Tour.find(queryObj);

使用find方法会返回一个query对象,上面有许多原型方法,因此可以做链条函数,但是如果find被异步执行,此时后面的链条函数将失效,因此我们需要:

//创建一个query
    const queryObj = { ...req.query };
    const excludeFields = ['page', 'sort', 'limit', 'fields'];

    excludeFields.forEach((el) => delete queryObj[el]);

    console.log(req.query, queryObj);

    const query = Tour.find(queryObj);
    // const query = Tour.find()
    //   .where('duration')
    //   .equals(5)
    //   .where('difficulty')
    //   .equals('easy');

    //执行查询
    const tours = await query;

    //发送请求

复杂查询

查询多项且带条件find

在这里插入图片描述
结果:

{
  difficulty: 'easy',
  page: '5',
  duration: { gte: '5' },
  price: { lt: '1500', gte: '100' }
}

使用正则表达式进行:

console.log(req.query);
    //深拷贝
    //创建一个query
    // filtering
    const queryObj = { ...req.query };
    const excludeFields = ['page', 'sort', 'limit', 'fields'];
    excludeFields.forEach((el) => delete queryObj[el]);

    //advanced filtering
    let queryStr = JSON.stringify(queryObj);
    queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, (match) => `$${match}`);
    console.log(JSON.parse(queryStr));

    const query = Tour.find(JSON.parse(queryStr));
    
    //{difficulty:'easy',duration:{$gte:5}}
    //{ difficulty: 'easy', page: '5', duration: { gte: '5' } }
    //gt gte lt lte

排序和筛选字段 sort、select

使用负号进行反选

	//sort
    if (req.query.sort) {
      const sortBy = req.query.sort.split(',').join(' ');
      console.log(sortBy);
      //由于find返回一个query对象,因此可以进行sort等链接操作
      query = query.sort(sortBy); //逆序仅需要传入负值
    } else {
      query = query.sort('-createdAt');
    }
    //限制字段
    if (req.query.fields) {
      const fields = req.query.fields.split(',').join(' ');
      query = query.select(fields);
    } else {
      query = query.select('-__v');
    }

分页操作:skip、 limit


    //分页:跳过前几页的操作,然后限制显示
    const page = req.query.page * 1 || 1;
    const limit = req.query.limit * 1 || 100;
    const skip = (page - 1) * limit;

    query = query.skip(skip).limit(limit);

    if (req.query.page) {
      const numTours = await Tour.countDocuments();
      if (skip >= numTours) throw new Error('This page does not exist');
    }

设置特定的路由进行特定查询

路由:

router
  .route('/top-5-cheap')
  .get(tourController.aliasTopTours, tourController.getAllTour);

设置参数进行特殊查询

exports.aliasTopTours = (req, res, next) => {
  req.query.limit = '5';
  req.query.sort = '-ratingAverage,price';
  req.query.fields = 'name,price,ratingAverage,summary,difficulty';
  next();
};

分离方法与controller,将其导入另一个文件

mongoDB聚合管道

使用聚合管道可对集合中的文档进行变换和组合
管道传入一个对象数组,数据库的数据将按传入的对象的顺序进行变化,后面的对象可以用前面定义好的变量
id将根据不同的取值创建不同的组,变量前面全部需要$

  • 各类字段参考手册官网
exports.getTourStats = async (req, res) => {
  try {
    const stats = await Tour.aggregate([
      {
        $match: { ratingsAverage: { $gte: 4.5 } },
      },
      {
        $group: {
          _id: '$difficulty',
          numTours: { $sum: 1 },
          numRating: { $sum: '$ratingsQuantity' },
          avgRating: { $avg: '$ratingsAverage' },
        },
      },
    ]);
      const plan = await Tour.aggregate([
    {
      $unwind: '$startDates'
    },
    {
      $match: {
        startDates: {
          $gte: new Date(`${year}-01-01`),
          $lte: new Date(`${year}-12-31`)
        }
      }
    },
    {
      $group: {
        _id: { $month: '$startDates' },
        numTourStarts: { $sum: 1 },
        tours: { $push: '$name' }
      }
    },
    {
      $addFields: { month: '$_id' }
    },
    {
      $project: {
        _id: 0
      }
    },
    {
      $sort: { numTourStarts: -1 }
    },
    {
      $limit: 12
    }
    console.log(stats);
};

添加虚拟字段

  • fat model and thin controller philosophy

向数据库添加一个虚拟字段并显示出来,但是添加的字段无法被查询
用途:将不必须加入数据库但展示需要的内容添加进去
必须使用func,不可以使用箭头函数,因为需要this字段,它指向文档本身

tourSchema.virtual('durationWeeks').get(function() {
  return this.duration / 7;
});

Mongoose 中间件(hook)

文档
类似于express的中间件,同样拥有next方法
分为前中间件和后中间件

document middleware

前中间件this指向文档,后中间件可以传入文档,但是this不指向文档

// DOCUMENT MIDDLEWARE: runs before .save() and .create()
tourSchema.pre('save', function(next) {
  this.slug = slugify(this.name, { lower: true });
  next();
});

// tourSchema.pre('save', function(next) {
//   console.log('Will save document...');
//   next();
// });

// tourSchema.post('save', function(doc, next) {
//   console.log(doc);
//   next();
// });

query middleware

也分为前与后
此时的this为一个query对象,可以向其添加自定义属性

由于find有find,findone,findmany,因此使用正则表达式可以匹配所有的方法

post获取到query返回的document,他的this可以获取到之前添加的属性

// QUERY MIDDLEWARE
// tourSchema.pre('find', function(next) {
tourSchema.pre(/^find/, function(next) {
  this.find({ secretTour: { $ne: true } });

  this.start = Date.now();
  next();
});

tourSchema.post(/^find/, function(docs, next) {
  console.log(`Query took ${Date.now() - this.start} milliseconds!`);
  next();
});

aggregation middleware

// AGGREGATION MIDDLEWARE
tourSchema.pre('aggregate', function(next) {
  this.pipeline().unshift({ $match: { secretTour: { $ne: true } } });

  console.log(this.pipeline());
  next();
});

const To

数据验证

  • fat model and thin controller philosophy

在创建范式时就定义好数据的需求,详见上文创建范式
在更新数据时使用需要加上字段:runValidators: true
使用npm库: validator.js

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值