美文网首页
nodejs入门

nodejs入门

作者: 杨志聪 | 来源:发表于2023-11-11 10:12 被阅读0次

node是一个用来执行javascript代码的运行时环境。本质上,node是一个集成了Chrome v8引擎的C++程序。

Chrome v8是目前世界上最快的javascript引擎,最早是为Chrome浏览器开发的。后来一个天才程序员Ryan Dahl把Chrome v8集成在一个C++程序中(这就是node的起源),于是javascript就从只能在浏览器环境中运行的脚本代码,变成可以直接在电脑上跑,并可以调用电脑的文件系统,网络等资源的代码了。于是javascript也可以做后端开发了。

我们使用node来开发高性能,可拓展的网络程序。node是开发RESTful服务的完美选择。

node是单线程的。意味着我们将会在一个线程中服务所有的客户。

node程序默认是异步的(或者理解为非阻塞)。这意味着node在处理I/O操作(网络请求或者访问文件系统)时,线程不会等待操作的结果,它会被释放去服务其他的客户。

所以,node的异步架构使它成为开发IO密集型程序的理想方案。

注意:由于node是单线程的,所以不要用node来开发CPU密集型的程序(例如视频编解码等),因为CPU密集型的操作会长时间占用线程,导致node异步的优势无法发挥出来。

node环境和浏览器环境的全局变量是不一样的。node中没有window和document等对象,相反,node有很多浏览器环境没有的对象,例如处理文件系统的对象,处理网络的对象,处理操作系统的对象等。

node核心概念

node没有window等浏览器环境存在的全局对象,不过node有一个全局对象叫global。

在浏览器环境中,变量默认会被添加在window全局对象中,但是node环境不会。在node中,每一个js文件都是一个模块。每一个js文件中声明的变量,其作用域只在该文件中,除非我们导出它:

module.exports = sayHi;

然后我们可以在另一个文件中导入使用:

const sayHi = require('./sayHi');

其实就是commonjs模块。

node有很多内置的模块,例如访问文件系统的模块,访问网络的模块等。EventEmitter是核心模块之一,很多内置的模块都是基于它完成的。通过继承EventEmitter,可以让我们的对象获得订阅和发布消息的能力。

NPM

所有的node程序都有一个package.json文件。package.json记录我们node程序的元数据,例如程序名字,程序版本,依赖包等。

我们使用npm从NPM社区下载第三方包,所有的第三方包(和第三方包自己的依赖包)都会保持在node_modules文件夹里。

node_modules文件夹默认从源代码管理中排除。

常用node命令:

// Install a package
npm i <packageName>
// Install a specific version of a package
npm i <packageName>@<version>
// Install a package as a development dependency
npm i <packageName> —save-dev
// Uninstall a package
npm un <packageName>
// List installed packages
npm list —depth=0
// View outdated packages
npm outdated
// Update packages
npm update
//To install/uninstall packages globally, use -g flag.

使用Express来搭建RESTful服务

REST定义了一组用于创建HTTP服务的约定:

  1. POST:创建资源;

  2. PUT:修改资源;

  3. GET:获取资源;

  4. DELETE:删除资源。

Express是一个简单、简约、轻量级的web构建框架服务器。使用Express可以很方便地构建一个RESTful服务。
安装Express:

npm i express

简单使用:

const express = require("express");
const app = express();
// Creating a course
app.post("/api/courses", (req, res) => {
  // Create the course and return the course object
  resn.send(course);
});
// Getting all the courses
app.get("/api/courses", (req, res) => {
  // To read query string parameters (?sortBy=name)
  const sortBy = req.query.sortBy;
  // Return the courses

  res.send(courses);
});
// Getting a single course
app.get("/api/courses/:id", (req, res) => {
  const courseId = req.params.id;

  // Lookup the course

  // If not found, return 404

  res.status(404).send("Course not found.");

  // Else, return the course object

  res.send(course);
});
// Updating a course
app.put("/api/courses/:id", (req, res) => {
  // If course not found, return 404, otherwise update it
  // and return the updated object.
});
// Deleting a course
app.delete("/api/courses/:id", (req, res) => {
  // If course not found, return 404, otherwise delete it
  // and return the deleted object.
});
// Listen on port 3000
app.listen(3000, () => console.log("Listening..."));

我们可以使用nodemon来监听javascript代码的更改和自动重启node程序。

我们可以使用环境变量来存储node程序的各种设置。在代码中我们可以通过process.env来访问环境变量。

// Reading the port from an environment variable
const port = process.env.PORT || 3000;
app.listen(port);

我们不能信任客户发送的任何数据!所以在保存客户的数据时,需要先验证一下数据是否有问题。Joi可以帮我们完成数据验证的工作。

Express进阶

中间件函数,是一个可以接收请求对象的函数,它可以提前结束一个请求,或者将这个当前请求传递给下一个中间件函数。

Expess有一些内置的中间件函数:

  1. json()。将请求body转换为json。
  2. urlencoded()。将请求body转换为URL-encoded payload。
  3. static()。支持静态文件服务。

通过中间件函数,我们可以实现日志打印,用户授权等功能。

// Custom middleware (applied on all routes)
app.use(function (req, res, next) {
  // ...
  next();
});

// Custom middleware (applied on routes starting with /api/admin)
app.use("/api/admin", function (req, res, next) {
  // ...
  next();
});

可以使用config来管理node程序的配置信息。

可以使用debug来添加node程序的调试信息(代替console.log)

通过模版引擎,我们可以为客户端返回HTML数据。pugEJS等都是比较常用的模版引擎。

mongodb

MongoDB是一个开源的文档数据库,它使用灵活的,类似JSON格式的文档来储存数据。

在关系型数据库里(例如MySql),我们有tables和rows,在MongoDB里我们有collections和documents。一个document可以包含sub-documents。

建议采用mongoose来操作mongoDB。

// Connecting to MongoDB
const mongoose = require("mongoose");
mongoose
  .connect("mongodb://localhost/playground")
  .then(() => console.log("Connected..."))
  .catch((err) => console.error("Connection failed..."));

要使用mongoDB储存数据,首先我们要定义一个mongoose schema,mongoose schema定义了MongoDB中document的形状。

// Defining a schema
const courseSchema = new mongoose.Schema({
  name: String,
  price: Number,
});

我们还可以使用SchemaType object来为mongoose schema添加更多属性:

// Using a SchemaType object
const courseSchema = new mongoose.Schema({
  isPublished: { type: Boolean, default: false },
});

mongoose schema支持的类型有String, Number, Date, Buffer (用来储存二进制数据), BooleanObjectID.

当我们定义好schema后,还需要将它转换成一个modal。modal可以看作是一个class,它是创建object的蓝图:

// Creating a model
const Course = mongoose.model("Course", courseSchema);

CRUD操作:

// Saving a document
let course = new Course({ name: "..." });
course = await course.save();

// Querying documents
const courses = await Course.find({ author: "Mosh", isPublished: true })
  .skip(10)
  .limit(10)
  .sort({ name: 1, price: -1 })
  .select({ name: 1, price: 1 });

// Updating a document (query first)
const course = await Course.findById(id);
if (!course) return;
course.set({ name: "..." });
course.save();

// Updating a document (update first)
const result = await Course.update({ _id: id }, { $set: { name: "..." } });

// Updating a document (update first) and return it
const result = await Course.findByIdAndUpdate(
  { _id: id },
  { $set: { name: "..." } },
  { new: true }
);

// Removing a document
const result = await Course.deleteOne({ _id: id });
const result = await Course.deleteMany({ _id: id });
const course = await Course.findByIdAndRemove(id);

在定义schema的时候,还可以通过SchemaType object对属性定义验证要求:

// Adding validation
new mongoose.Schema({
  name: { type: String, required: true },
});

Mongoose会在保持数据到mongoDB之前执行验证逻辑。我们也可以通过调用validate()方法手动执行验证逻辑。

内置的验证方法:

  • Strings: minlength, maxlength, match, enum
  • Numbers: min, max
  • Dates: min, max
  • All types: required

我们也可以自定义验证方法:

const userSchema = new Schema({
  phone: {
    type: String,
    validate: {
      validator: function (v) {
        return /\d{3}-\d{3}-\d{4}/.test(v);
      },
      message: (props) => `${props.value} is not a valid phone number!`,
    },
    required: [true, "User phone number required"],
  },
});

验证方法可以定义为异步的(有些验证方法可能需要执行从数据库读取数据等异步操作):

validate: {  
  isAsync: true 
  validator: function(v, callback) {
    // Do the validation, when the result is ready, call the callback
    callback(isValid);  
  }
}

其他SchemaType比较常用的属性:

  • Strings: lowercase, uppercase, trim
  • All types: get, set (to define a custom getter/setter)
price: { 
  type: Number, 
  get: v => Math.round(v), 
  set: v => Math.round(v)
}

mongoDB进阶

关联数据

mongoDB是非关系型数据库,所以它没有类似于MySql的JOIN等操作方式进行联表查询。在mongoDB中要实现两个有关联的数据,有两种方式:

  1. 通过引用的方式。一个数据保存另一个数据的ObjectId;
  2. 通过嵌套的方式。一个数据中潜逃另一个数据。

方式1的优点是能保持两个数据的独立性,缺点是查询速度变慢,因为要查询两个记录。

方式2的优点是查询速度快(只需要查询一个记录),缺点是被嵌套的数据如果不只嵌套在一个地方,那么要保证这个数据的同步更新!如果同步的过程出现错误,那么这个数据在不同的地方可能是不一致的。

使用那种关联方式,要看你是否能接受数据可能不同步的情况,如果不能接受,就使用方式1。

通过引用的方式关联数据:

// Referencing a document
const courseSchema = new mongoose.Schema({
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "Author",
  },
});

通过嵌套的方式关联数据:

// Referencing a document
const courseSchema = new mongoose.Schema({
  author: {
    type: new mongoose.Schema({
      name: String,
      bio: String,
    }),
  },
});

被嵌套的的documents没有自己的save方法,它们只能在它的parent的上下文中保存。

// Updating an embedded document
const course = await Course.findById(courseId); 
course.author.name = "New Name"; 
course.save();

事件

在MySql之类的数据库中,可以通过事件机制,可以保证若干个不同的表同时更新数据。mongoDB没有事件机制。为了实现类似事件的机制,我们可以使用一种叫“Two Phase Commit”的保存方式。可以使用Fawn来实现事件的效果。

ObjectID

ObjectID由MongoDB driver生成,用来唯一标记一个document,它由12个字节组成:

  • 4 bytes: timestamp
  • 3 bytes: machine identifier
  • 2 bytes: process identifier
  • 3 byes: counter

尽管使用了这么严密的方式来保证ObjectID的唯一性,但是还是有1/16,000,000的几率会生成两个一样的ObjectID!

我们可以使用joi-objectid 来验证一个ObjectID是否有效。

验证和授权

验证(Authentication):一般是通过账号和密码,验证一个用户是否有效。

授权(Authorization):决定一个用户是否有权限进行某项操作。

在保存用户信息时,对于用户密码,不能把密码原文保存在数据库!一般都是保存密码的hash值。

我们可以使用bcrypt来hash密码:

// Hashing passwords
const salt = await bcrypt.genSalt(10);
const hashed = await bcrypt.hash("88888888", salt);
// Validating passwords
const isValid = await bcrypt.compare("88888888", hashed);

验证通过后,我们在服务器中生成一个JSON Web Token (JWT) 返回给客户端,客户端后面每次发起请求时,都要携带JWT参数,服务器通过验证JWT中携带的参数,来决定这个请求的权限。

JSON Web Token (JWT)是一个被编码成一个长字符串的JSON数据,它类似于通行证或者司机的驾照,它包含了用户的信息(例如用户ID,用户身份等)。它不能被篡改,因为修改JWT需要重新生成数字签名。

一般JWT不要保存在服务端,否则一旦服务器被攻击,JWT落入黑客手里就危险了。JWT由客户端保存就行了。

我们可以使用jsonwebtoken来生成JWT。

// Generating a JWT
const jwt = require(‘jsonwebtoken’);
// Generating a JWT
const jwt = require('jsonwebtoken');
const token = jwt.sign({ _id: user._id}, 'privateKey');

永远不要储存私钥和其他密码在代码中!要储存在环境变量中!

授权(Authorization)操作可以放在中间件函数中执行。当JWT无效时,返回401,当JWT有效,但是没有操作权限时,返回403。

处理和打印错误

错误是无法避免的,无论是代码的bug,还是其他不可抗因素。作为一个优秀的开发者,需要把程序运行的错误信息记录下来。

node程序运行时,有三种类型的错误:

  1. 请求处理pipeline中出现的错误。也就是中间件函数中出现的错误。

  2. uncaughtException。

  3. unhandledRejection。

要捕捉第一种错误,可以在所有路由的最后面,注册error middleware:

app.use(function (err, req, res, next) {
  // Log the exception and return a friendly error to the client.
  res.status(500).send("Something failed");
});

而且要保证每个中间件函数都把错误传递下去:

app.use(function (req, res, next) {
  try {
    // do something
  } catch (error) {
    // 把错误信息传递下去!
    next(error);
  }
});

每个中间件函数都要手动添加trycatch非常不方便,可以使用express-async-errors解决这个问题。

我们可以使用process.on('uncaughtException')process.on('unhandledRejection')来捕捉另外两种错误。当这两种错误出现后,最好重新启动程序,因为这意味着程序运行的环境可能不干净了。

捕捉到错误信息后,可以把信息打印在控制台,也可以保存在文件,也可以保存在数据库里,或者开发环境和发布环境采用不一样的打印策略。可以使用winston来实现这些需求。

相关文章

  • React Native的极简手册

    安装入门 安装入门可以参考:React Native官方文档。 NodeJS知识储备:参考《NodeJS入门》。(...

  • Node入门到入门(Windows)

    Node入门到入门(Windows) 安装NodeJS和NPM 1.安装NodeJS和NPM ​ 打开...

  • nodejs入门

    nodejs入门 花了点时间整理了下nodejs入门的图谱,如果将整个图谱的点都过了一次,相信你的nodejs知识...

  • NODE.JS

    入门 NODE安装 http://www.runoob.com/nodejs/nodejs-install-set...

  • nodejs 学习路线

    <1--nodejs入门> 1.准备-- js语言入门: -- JavaScript 教程 ...

  • 库&插件&框架&工具

    nodejs 入门 nodejs 入门教程,大家可以在 github 上提交错误2016 年最好用的表单验证库 S...

  • Node.js文档和教程

    七天学会NodeJS:https://nqdeng.github.io/7-days-nodejs/Node入门:...

  • 2018-01-03

    学习顺序以及资源 node 入门 《nodejs入门》 如何系统地学习Node.js?

  • StarUml3.0安装破解及Nodejs使用

    StarUml+Nodejs入门 安装Nodejs 1. 从官网上下载nodejs的安装包 2. 检验是否安装成功...

  • Win10部署Docker + mongodb + node.j

    本文主要参考了docker入门nodejs+mongodb以及Nodejs 应用简单的访问Mongodb 部署至D...

网友评论

      本文标题:nodejs入门

      本文链接:https://www.haomeiwen.com/subject/izdabdtx.html