node不是一门编程语言,也不是框架, node.exe是js代码可以在浏览器外跨平台的直接运行于计算机上的运行环境,类似于java的JVM虚拟机。 核心运用的是谷歌一样的V8引擎。
优势,可以前后端都用js来写,更方便程序的运行,在浏览器运用V8引擎来运行JS,而node可以运行JS而不需要浏览器。
node是异步架构所以没有阻塞,优势是可扩展性强。
同步或阻塞是指他的一个线程可以来处理所有请求,当他的线程处理完一个请求才转到另一个线程处理另一个比如ASP.NET。而异步是指一个线程处理一个请求时如果这个线程在等待,则他就先处理另一个线程的任务,等一会儿再回来处理,这样就不用浪费时间。node就是利用异步无阻塞。
node则但是如果这个任务一直占用他则不能转到其他的,所以node不适合用来处理运算密集型任务,比如video 程序,因为需要需要一直占用线程。而他更适合处理数据密集型的实时程序,每个请求不会一直占用线程。
安装node直接官网下载进行安装npm会一同安装上,node --version检查当前版本
运行node 我们可以新建一个文件夹然后创建一个js文件,node app.js来运行
在node应用中每个文件都是一个module,每个文件中是彼此隔离的,一个文件不能使用另一个文件中的变量。
console.log("d");
//window.console.log("dadf");//这里会报错,因为node中没有window对象,但是在客户端,浏览器中这就不会报错
global.console.log("dafaa");//在node中是globle
var b = 2;//我们这里定义的变量不像浏览器中是全局的,这里的变量范围只在此文件也就是此module
console.log(global.b);//undefined 找不到变量,global是全局的所有文件都可以接触到的。
//app.js
url = "att.ac.uk";
function log(url) {
console.log(url);
}
//直接导出log方法
module.exports = log;//这里不能直接写exports因为他是另一个引用
//log.js
const a = require('./app'); //导入文件 ./代表当前文件夹默认文件后面加.js后缀
//../node_study/app可以遍历所有文件夹
//./subfolder/app如果是在子文件夹下
a("da");
console.log(__dirname);//这是隐藏变量
console.log(__filename);
//path module
const path =require('path');
let pathobj = path.parse(__filename);
console.log(pathobj);
/*
{ root: '/',
dir: '/Users/chongbin/Desktop/project/web_project/node_study',
base: 'log.js',
ext: '.js',
name: 'log' }
*/
//os module 可以用Node来接触到计算机内部信息
const os = require('os');
let total = os.totalmem();
let free = os.freemem();
//可以自定义格式化
console.log(`total mememory is : ${total}
free memeory is : ${free}`);
/*
total mememory is : 8589934592
free memeory is : 24526848
*/
//EventEmitter module 事件监听模块,包括Http监听都是以此为基础
const EventEmitter = require('events');
const emitter = new EventEmitter();
//register an listener 类似于安卓的事件监听,要先监听然后raise才可以监听到
emitter.on('message', (args)=> {
console.log('message is coming:',args);
}
);
//raise an event
emitter.emit('message',{id:4,name:'bin'});//第一个参数为flag,第二个参数为传参数据
创建一个类继承emitter来实现监听
//log.js
const EventEmitter = require('events');
class Logger extends EventEmitter {
log(message) {
console.log('sending :' + message);
//在类的方法中注册emit
this.emit('messagelog', {
id: 1,
url: 'http://dafadf'
});
}
}
module.exports = Logger;
//app.js
const Logger = require('./log');
const logger = new Logger(); //创建对象
//监听
logger.on('messagelog', (args) => {
console.log('listen---', args);
});
//调用方法
logger.log('ddd');
http 用的就是继承的event emitter来实现http服务器创建:
const http = require('http');
//创建服务并监听事件
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.write('this is the root route');
res.end();
}
if (req.url === '/api') {
res.write('this is the api path');
res.end();
}
});
//设置监听端口
server.listen(3000);
console.log('listening is on 3000.....');
这时console会打印listening is on 3000.....
服务启动
然后在浏览器输入http://localhost:3000/api
来访问服务器,则服务器会回应res浏览器页面显示this is the api path'
当然实际项目代码中,我们会使用express框架来写HTTP,而不像上述代码这样如果处理很多请求代码会非常Ugly。
npm是一个安装所有第三方工具和库的软件工具,NPM一个命令行管理软件,用它来执行一些操作。
npm init --yes
来生成package.json文件,这个文件列出用了哪些库。
npm i
执行此命令来安装package下配置的所有依赖包,这些安装的包都会保存在node_modules 文件夹里,所以当你上传代码到github或者把代码发送给其他人的时候,node_modules文件夹可以删掉,然后其他人直接npm i 就可以安装所有依赖了。
npm outdated
会列出当前依赖包版本和该包官网最新版本,如果包含的依赖是最新版本则不会列出
npm update
可以用来更新包,但是只能更新小版本的包也就是4.x.x之后的包,小版本的更新是对项目没有影响的都可以使用
npm i xxx
用来安装或更新某个包,如果现在是^4.2.1
会更新到4.X.X的最新版本,~4.3.4
会更新到4.3.X版本,如果想要更新到确切版本,则直接用数字不带符号
npm un XXX
来卸载某个依赖包
如果依赖已经过时,这时可以直接修改package.json文件更改到相应的最新依赖版本,手动直接npm i xxx
进行安装
npm i -g xxx
加上-g就是全局安装,也就是在电脑上安装,比如我把eslint安装到电脑中,则任何项目都可以直接使用。mac上加权限sudo
npm outdated -g
来检查电脑中所有过时的包
npm list --depth=0 -g
来列出电脑中已经安装的包
使用全局npm 安装eslint
eslint是用来提示js语法错误的插件。比jshint好用,jsHint会出现一些未知错误。
在vscode中使用eslint
npm i -g eslint
,这样以后就不用在单独项目中重复安装eslint了。.eslintrc.json
文件,也就是eslint配置文件,不配置不好使{
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"semi": 2
}
}
则eslint可以正常使用了, ecmasVersion=2018则可以支持最新版的语法比如const
所有安装 的依赖都会在package.json文件中展示出来
package.json 是一个项目说明文件和添加依赖dependency文件
{
"name": "node_js_test",
"version": "1.0.0",
"description": "test",
"main": "app.js",
"dependencies": { //项目依赖
"express": "^4.16.4",
"mongoose": "^5.3.4",
"test": "^0.6.0",
"underscore": "^1.9.1"
},
"devDependencies": {
"jshint" :"2.4.3"
}, //开发项目依赖,而不是项目本身的依赖,也就是说是便于开发者使用的一些工具比如JShint提示语法错误的工具。
"scripts": {//npm run script中的关键字来实现script的内容
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"for",
"test"
],
"author": "charless",
"license": "ISC"
}
const _ = require('underscore');
//require加载顺序:1:core module 2:file or folder 3.node_modules文件夹
let b = _.contains([1,2,3],2);
console.log(b);//true
ES6中bable工具把ES6代码转化成所有浏览器可以执行的es5 JS语言,相当于一个转化器和编译器
而Bundle是用来把所有的js文件合并成一个Js文件的工具,现在最流行的是webpack。运用webpack一个很大的功能就是,可以运用wepack的watch 来一直监听代码如果修改代码,不用重新编译程序,他会自动重新编译然后执行程序。
当上传github上时可以忽略你不想上传的东西。添加.gitignore
文件在里面添加一行node_modules/
斜杠代表文件夹,这样这个文件夹就不会被上传
.gitignore
文件实例:
.DS_Store
node_modules/
npm-debug.log
.idea/
dump.rdb
.vscode/
public/*
dist
coverage/
.nyc_output/
yarn.lock
http method : get 查 post 增 put 改 delete 删
restful 是一个提供http服务的风格,也就是http 的增删改查方法以及用其后面的endpoint来操作数据,以下风格就是restful
这里http是协议,vidly.com是网站域名
/api/customers及后面的为endpoint也就是要访问的resource
http的CRUD operation增删改查操作:create read update delete
GET /api/customers 得到数据
GET /api/customers/1 得到指定id数据
PUT /api/customers/1 更新指定数据
DELETE /api/customer/1 删除指定数据
POST /api/customers 增加数据
express 是用来实现web 程序的一个轻量级框架,用它来实现http service的代码框架,因为本身的http代码实现较多操作是会非常复杂难懂,在express中,http的方法都被封装到express中了,以便于更方便的使用,让代码维护起来更方便,用它能更简便的实现web server服务。
项目中npm i express
安装
下面实现一个简单的web server
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('hello world!!!!!!!!!');
});
//浏览器访问 http://localhost:3000/api/course 会得到数组[1,2,3]
app.get('/api/course', (req, res) => {
res.send([1, 2, 3]);
});
//route parameter
//请求http://localhost:3000/api/course/2
app.get('/api/course/:id', (req, res) => {
res.send(req.params.id); //2
});
//query paramater
app.get('/api/course/:year/:month', (req, res) => {
//请求http://localhost:3000/api/course/2019/9
console.log(req.params); //对象 { year: '2019', month: '9' }
//请求http://localhost:3000/api/course/2019/9?sortBy=name
console.log(req.query); //附加信息{ sortBy: 'name' }
//res.send(`${req.params }+ ${req.query}`);//如果连着写,浏览器打印是object,不知道是否接收到对象
res.send(req.query);//只能send一次
});
//用port环境变量,所以当设置环境变量的时候就会用环境变量,如果没设置就用3000
const port = process.env.PORT || 3000; //process是个global下面的属性
app.listen(port, () => console.log(`port ${port} is lisening...`));
express具体更多操作可以参考express官网有非常简明清楚的教程。
监听node,用这个命令运行服务,则当你修改代码保存的时候,他会自动重启node服务并执行,就不需要每次修改都停掉服务重新运行程序
安装:
npm i nodemon -g
全局安装
使用:
nodemon index.js
来替代node index.js
然后每次保存文件的时候会自动部署执行程序,不需要手动停掉服务器重新运行代码。
客户端测试HTTP方法可以选用Restlet clinet浏览器插件
比如测试post,选择post方法,body选择text,json格式然后输入json数据,发送。
下拉查看返回内容,返回内容body选择pretty则显示的是对象。
const express = require('express'); //npm i express
const app = express();
const Joi = require('joi'); //用来做输入验证,npm i joi
app.use(express.json()); //用来做req.body的字符串转换
//设置一个courses数组类似于database存储数据
const courses = [{
id: 1,
name: 'course 1'
},
{
id: 2,
name: 'course 2'
},
{
id: 3,
name: 'course 3'
}
];
//get方法 得到数据
app.get('/', (req, res) => {
res.send('hello world!!!!!!!!!');
});
// 用get方法或浏览器访问http://localhost:3000/api/course
app.get('/api/course', (req, res) => {
console.log(courses); //打印对象
res.send(courses);
});
//get指定Id
app.get('/api/course/:id', (req, res) => {
//find方法为数组方法,里面用回调来进行查找对应id,params的id是个string类型需要转成int
console.log(req.params); //{ id: '2' }
const course = courses.find((c) => c.id === parseInt(req.params.id));
//如果没有找到course,也就是对象为falsy
//http://localhost:3000/api/course/4
if (!course) { //返回404,资源没有找到
res.status(404).send('course not found');
return;
}
//http://localhost:3000/api/course/2
console.log(course); //{ id: 2, name: 'course 2' }
res.send(course); //res.send相当于return
});
//post方法,添加数据
app.post('/api/course', (req, res) => {
//值放在body中传入,这里如果文件中不用app.use(express.json());语句则无法转换json字符串express的req.body就是undifned
console.log(req.body); //{ id: 5, name: 'binbin' }
//验证
const result = validateCourse(req.body);
console.log(result);
if (result.error) {
res.status(400).send(result.error); //返回400,请求不合法
return;
}
//要添加的数据
const cour = {
id: courses.length + 1,
name: req.body.name
};
courses.push(cour);
//如果不回res则客户端会一直处于等待状态,所以一定要返回res.send
res.send(courses);
});
//put方法,更新数据
app.put('/api/course/:id', (req, res) => {
//可以复制以上实现逻辑代码
//1.查找id
const course = courses.find((c) => c.id === parseInt(req.params.id));
if (!course) { //返回404,资源没有找到
res.status(404).send('course not found');
return;
}
//2.验证请求合法性
req.body.id = req.params.id; //把id给他作为参数传入验证
const result = validateCourse(req.body);
console.log(result);
if (result.error) {
res.status(400).send(result.error); //返回400,请求不合法
return;
}
//3.更新数据
course.name = req.body.name;
res.send(course);
});
//delete 删除
app.delete('/api/course/:id', (req, res) => {
//1.查找
const index = parseInt(req.params.id);
const course = courses.find((c) => c.id === index);
if (!course) { //返回404,资源没有找到
res.status(404).send('course not found');
return;
}
//2.找到删除
courses.splice(index - 1, 1);
res.send(course);
});
//方法: 验证输入合法性
function validateCourse(course) {
//利用Joi 进行客户端输入验证
const schema = {
id: Joi.number().required(),
name: Joi.string().min(3).required()
};
const result = Joi.validate(course, schema);
return result;
}
//用port环境变量,所以当设置环境变量的时候就会用环境变量,如果没设置就用3000
const port = process.env.PORT || 3000; //process是个global下面的属性
app.listen(port, () => console.log(`port ${port} is lisening...`));
middleware function他是接收request并返回res的中间件,或者传给另一个中间件。app.use().就是middleware function。应该说app.的方法都是中间件方法。
比如app.get就是一个middleware function,他接收request并返回res。
而app.use(express.json());
也是一个middleware function他接收一个json字符串并转换成对象赋值给req.body属性,传递到另一个middleware function.
//app.js
const express = require('express'); //npm i express
const logger = require('./logger');
const Joi = require('joi'); //第三方中间件方法
const app = express();
app.use(express.json()); //用来做req.body的字符串转换
app.use(logger);//使用自定义中间件
app.use(express.static('public'));//使用静态文件,public文件夹中存放静态文件可以直接浏览器/readme.txt来访问public文件夹下的文件
//logger.js
function log(req, res, next) {
console.log("logging....");
next();//会向下执行下一个middleware function
}
module.exports = log;
//npm i debug
//只要在启动nodemon时输入 DEBUG=app:startup nodemon app.js
//想在哪个文件中使用就在该Module上引用
const debug = require('debug')('app:startup');
//'app:startup'可以自行设置标题
debug('binbibnibnbin');
这样就可以在控制台看到debug的信息了而不用console.log来每次打印输出。而且默认debug是不会输出的,只有当DEBUG=app:startup nodemon app.js
启动时才会有debug输出,这样在以后也不用删除或注释掉console.log。
输出如下图
还会有特殊颜色显示,非常好辨认
把不同功能的文件放到不同的文件下面,让文件更有序。
index.js
const express = require('express'); //npm i express
const app = express();
const courses = require('./routes/courses');//引入courses文件
app.use(express.json()); //用来做req.body的字符串转换
app.use('/api/course', courses);//导航路径
//用port环境变量,所以当设置环境变量的时候就会用环境变量,如果没设置就用3000
const port = process.env.PORT || 3000; //process是个global下面的属性
app.listen(port, () => console.log(`port ${port} is lisening...`));
./routes/courses.js
const Joi = require('joi'); //用来做输入验证,npm i joi
const debug = require('debug')('app:startup');//引入debug代替console
const express = require('express');
const router = express.Router();
//设置一个courses数组类似于database存储数据
const courses = [{
id: 1,
name: 'course 1'
},
{
id: 2,
name: 'course 2'
},
{
id: 3,
name: 'course 3'
}
];
// 用get方法或浏览器访问http://localhost:3000/api/course
router.get('/', (req, res) => {
debug(courses); //打印对象
res.send(courses);
});
//get指定Id
router.get('/:id', (req, res) => {
//find方法为数组方法,里面用回调来进行查找对应id,params的id是个string类型需要转成int
debug(req.params); //{ id: '2' }
const course = courses.find((c) => c.id === parseInt(req.params.id));
//如果没有找到course,也就是对象为falsy
//http://localhost:3000/api/course/4
if (!course) { //返回404,资源没有找到
res.status(404).send('course not found');
return;
}
//http://localhost:3000/api/course/2
debug(course); //{ id: 2, name: 'course 2' }
res.send(course); //res.send相当于return
});
//post方法,添加数据
router.post('/', (req, res) => {
//值放在body中传入,这里如果文件中不用router.use(express.json());语句则无法转换json字符串express的req.body就是undifned
debug(req.body); //{ id: 5, name: 'binbin' }
//验证
const result = validateCourse(req.body);
debug(result);
if (result.error) {
res.status(400).send(result.error); //返回400,请求不合法
return;
}
//要添加的数据
const cour = {
id: courses.length + 1,
name: req.body.name
};
courses.push(cour);
//如果不回res则客户端会一直处于等待状态,所以一定要返回res.send
res.send(courses);
});
//put方法,更新数据
router.put('/:id', (req, res) => {
//可以复制以上实现逻辑代码
//1.查找id
const course = courses.find((c) => c.id === parseInt(req.params.id));
if (!course) { //返回404,资源没有找到
res.status(404).send('course not found');
return;
}
//2.验证请求合法性
req.body.id = req.params.id; //把id给他作为参数传入验证
const result = validateCourse(req.body);
debug(result);
if (result.error) {
res.status(400).send(result.error); //返回400,请求不合法
return;
}
//3.更新数据
course.name = req.body.name;
res.send(course);
});
//delete 删除
router.delete('/:id', (req, res) => {
//1.查找
const index = parseInt(req.params.id);
const course = courses.find((c) => c.id === index);
if (!course) { //返回404,资源没有找到
res.status(404).send('course not found');
return;
}
//2.找到删除
courses.splice(index - 1, 1);
res.send(course);
});
//方法: 验证输入合法性
function validateCourse(course) {
//利用Joi 进行客户端输入验证
const schema = {
id: Joi.number().required(),
name: Joi.string().min(3).required()
};
const result = Joi.validate(course, schema);
return result;
}
module.exports = router;
用promise实现异步
const debug = require('debug')('app:startup');
debug('before');
//promise会分别异步执行命令。
const p1= new Promise((resolve,reject)=>{
setTimeout(()=>{
debug('asynch 1......');
resolve(1);//如果成功用resolve返回1
},4000);
});
const p2= new Promise((resolve,reject)=>{
setTimeout(()=>{
debug('asynch 2......');
//resolve(2);//如果成功用resolve返回2
reject('f');//如果执行失败则用reject
},2000);
});
Promise.all([p1,p2])
.then(result=>debug(result))//当返回resolve则执行then方法
.catch(reject=>debug('failed'));//返回reject失败则执行catch方法
debug('after');
OUTPUT
一个是成功都返回resolve,第二个是有一个失败返回reject则程序失败
直接官网下载tgz压缩包,然后双击解压成文件夹,直接就可以使用了。
mac在.bash_profile文件里设置mongodb的path为解压包所在bin目录,我的是
export PATH=${PATH}:/Users/chongbin/Desktop/mongodb/mongodb-osx-x86_64-4.0.5/bin
然后一个存储数据db的文件夹,可以自己指定路径,然后terminal输入
mongod --dbpath /Users/chongbin/Desktop/mongodb/data/db
即可开启Mongodb。
可以用Mongodb compass来连接管理mongodb服务器。
增删改查到数据库
首先我们要设置一个schema,然后用schema生成对应的类,然后创建对象,把这个对象存入数据库,这个对象在数据库中也就是对应的document
以下是schema可以设置的类型
增删改查实例代码
// npm i mongoose
const mongoose = require('mongoose');
const debug = require('debug')('app:startup');
//连接Mongodb
mongoose.connect('mongodb://localhost/nodestudydb', { //创建名称为nodestudydb的数据库
useNewUrlParser: true
}) //会返回一个promise
.then(() => debug('connected database....')) //连接成功
.catch((err) => debug('could not connect mongodb')); //连接失败
//创建schema,相当于mongodb中的collection
const courseSchema = new mongoose.Schema({
name: String,
author: String,
tags: [String],
date: {
type: Date,
default: Date.now
},
isPublished: Boolean
}, { collection: 'courseCollection' });//设置collection名称
//创建类
const Course = mongoose.model('Courese', courseSchema);
//存入数据, 利用异步方法存储数据到Mongodb
async function creatCourse() {
const course = new Course({
name: 'leilei',
author: 'asdfasdf',
tags: ['node', 'backend'],
isPublished: true
});
const result = await course.save();
debug(result);
}
//creatCourse();
//查找数据
async function getCourse() {
const courses = await Course
.find()
.limit(10) //设置查找10条数据
.sort({name:1})//设置排序,按name升序排序
//.count(); 计算数据条数而不显示具体信息
.select({name:1,author:1});//设置属性选择,只显示name和author两个属性
debug(courses);
}
//getCourse();
//更新数据:方法1 先查询再更新
async function updateCourese1(id) {
const course = await Course.findById(id);
if(!course) return;//如果没找到
//找到了则设置新的数据
course.set({
isPublished: true,
author: 'alalallala'
});
//保存
const result = await course.save();
debug(result);
}
//updateCourese1('5c45fb367a086d0c945b4275');
//更新数据:方法2 直接更新
async function updateCourese2(id) {
const result = await Course.update({_id : id},{//我们可以在这了设置条件比如这里是id,我们也可以设置其他条件比如author:'dd’符合的进行更新
$set:{
author:'update2',
isPublished: false
}
});
debug(result);
}
//updateCourese2('5c45fb367a086d0c945b4275');
//删除数据
async function removeCourse(id) {
const result = await Course.deleteOne({ _id:id });//删除一条数据,如果是条件内有多条则删除第一条
//const result = await Course.deleteMany({ isPublished: false});//删除符合条件的多条数据
debug(result);
}
//removeCourse('5c45fb367a086d0c945b4275');
//条件查找数据
//eq (equal)
//ne (not equal)
//gt (greater than)
//gte (greater than or equal to)
//lt (less than)
//lte (less than or equal to)
//in
//nin (not in)
//or
//and
//正则表达式
async function getCourse() {
const courses = await Course
.find({author: 'chocho',isPublished : ture})//如果find中不加参数则全部查找,加参数条件查找
.find({price: {$gt: 10 } })//查找price属性大于10的
.find({price: {$gt: 10, $lt: 20 } })//查找price属性大于10且小于20的
.find({price: {$in: [10,15,20] } })//查找price属性等于10,15 和20 的
.find().or([{author: 'chocho'},{isPublished : ture}])//查找author条件或者isPublished条件
//正则
.find({author: /^Bin/i})//查找以字符串bin为开头的author,后面加i 代表忽略大小写
.find({author: /Bin$/})//查找以Bin为结尾的author
.find({author: /.*Bin.*/})//查找字符串中包含bin的
debug(courses);
}
//依据页数编号查找数据
async function getCourse() {
const pageNumber = 2;
const pageSize = 10;
const courses = await Course
.find()
.skip((pageNumber-1) * pageSize)
.limit(pageSize); //设置查找数据的条数
}
具体代码详见我的github-mern_backend_demo实现了一个简单的后端服务并连接到本地mongodb。
前端可以通过restful测试,如图: