Node.js完全指南:从入门到精通
一、Node.js基础概念
1.1 什么是Node.js?
Node.js是一个基于Chrome V8引擎的JavaScript运行环境,让JavaScript可以在服务器端运行。它使用事件驱动、非阻塞I/O模型,使其轻量且高效。
1.2 Node.js的历史
- 2009年:Ryan Dahl创建了Node.js
- 2010年:NPM(Node Package Manager)诞生
- 2011年:npm 1.0发布
- 2015年:Node.js基金会成立
- 2016年:引入长期支持(LTS)版本
- 至今:持续快速发展,广泛应用于后端开发
1.3 Node.js的特点
- 单线程:使用单线程事件循环,避免线程切换开销
- 异步非阻塞I/O:提高并发处理能力
- 跨平台:可在Windows、Linux、Mac上运行
- NPM生态:拥有世界上最庞大的开源库生态系统
- JavaScript全栈:前后端统一语言,降低开发成本
1.4 Node.js的应用场景
- Web应用:RESTful API、GraphQL服务
- 实时应用:聊天应用、实时协作工具
- 微服务:构建分布式系统
- CLI工具:命令行工具开发
- 数据流:处理大量数据
- 物联网:IoT设备数据处理
二、Node.js安装和环境配置
2.1 安装Node.js
Windows系统
- 访问Node.js官网:https://nodejs.org/
- 下载LTS版本(推荐)或Current版本
- 运行安装程序,按提示完成安装
- 验证安装:
node--versionnpm--versionLinux系统
# 使用包管理器安装# Ubuntu/Debiansudoapt-get update sudoapt-getinstall nodejs npm# CentOS/RHELsudo yum install nodejs npm# 使用NVM安装(推荐)curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh |bashsource ~/.bashrc nvm install--lts nvm use --ltsMac系统
# 使用Homebrew安装 brew installnode# 使用NVM安装curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh |bashsource ~/.bashrc nvm install--lts2.2 版本管理
使用NVM(Node Version Manager)
# 安装NVMcurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh |bash# 查看可用版本 nvm list-remote # 安装特定版本 nvm install16.14.0 # 使用特定版本 nvm use 16.14.0 # 设置默认版本 nvm alias default 16.14.0 # 查看已安装版本 nvm list # 卸载版本 nvm uninstall 16.14.0 使用n(简单版本管理器)
# 安装nnpminstall-g n # 安装最新LTS版本 n lts # 安装最新版本 n latest # 安装特定版本 n 16.14.0 # 切换版本 n # 查看已安装版本 n ls2.3 环境变量配置
# Windows系统# 系统环境变量中添加:# NODE_PATH = C:\Users\YourName\AppData\Roaming\npm\node_modules# PATH = %NODE_PATH%;C:\Program Files\nodejs# Linux/Mac系统# 在 ~/.bashrc 或 ~/.zshrc 中添加:exportNODE_PATH=/usr/local/lib/node_modules exportPATH=$PATH:/usr/local/bin # 重新加载配置source ~/.bashrc 2.4 创建第一个Node.js应用
// hello.js console.log('Hello, Node.js!');// 运行 node hello.js // http-server.jsconst http =require('http');const server = http.createServer((req, res)=>{ res.writeHead(200,{'Content-Type':'text/plain'}); res.end('Hello, Node.js!');}); server.listen(3000,()=>{ console.log('Server running at http://localhost:3000/');});// 运行 node http-server.js 三、模块系统
3.1 CommonJS模块系统
导出模块
// math.js// 方式1:导出单个值 module.exports ={add:function(a, b){return a + b;},subtract:function(a, b){return a - b;}};// 方式2:导出多个值 exports.add=function(a, b){return a + b;}; exports.subtract=function(a, b){return a - b;};// 方式3:导出类classCalculator{add(a, b){return a + b;}} module.exports = Calculator;导入模块
// app.js// 方式1:导入整个模块const math =require('./math'); console.log(math.add(1,2));// 3// 方式2:解构导入const{ add, subtract }=require('./math'); console.log(add(1,2));// 3// 方式3:导入类const Calculator =require('./math');const calc =newCalculator(); console.log(calc.add(1,2));// 33.2 ES6模块系统
启用ES6模块
// package.json{"type":"module"}导出模块
// math.mjs// 方式1:命名导出exportfunctionadd(a, b){return a + b;}exportfunctionsubtract(a, b){return a - b;}// 方式2:默认导出exportdefaultclassCalculator{add(a, b){return a + b;}}// 方式3:混合导出exportconstPI=3.14159;exportfunctionmultiply(a, b){return a * b;}导入模块
// app.mjs// 方式1:导入命名导出import{ add, subtract }from'./math.mjs'; console.log(add(1,2));// 3// 方式2:导入默认导出import Calculator from'./math.mjs';const calc =newCalculator(); console.log(calc.add(1,2));// 3// 方式3:导入所有import*as math from'./math.mjs'; console.log(math.add(1,2));// 3// 方式4:动态导入const module =awaitimport('./math.mjs'); console.log(module.add(1,2));// 33.3 核心模块
1. fs(文件系统)
const fs =require('fs');const path =require('path');// 同步读取文件try{const data = fs.readFileSync('input.txt','utf8'); console.log(data);}catch(error){ console.error('读取文件失败:', error);}// 异步读取文件(推荐) fs.readFile('input.txt','utf8',(error, data)=>{if(error){ console.error('读取文件失败:', error);return;} console.log(data);});// 使用Promiseconst fsPromises =require('fs').promises;asyncfunctionreadFileAsync(){try{const data =await fsPromises.readFile('input.txt','utf8'); console.log(data);}catch(error){ console.error('读取文件失败:', error);}}// 写入文件 fs.writeFile('output.txt','Hello, Node.js!','utf8',(error)=>{if(error){ console.error('写入文件失败:', error);return;} console.log('文件写入成功');});// 追加内容 fs.appendFile('output.txt','\n追加的内容','utf8',(error)=>{if(error){ console.error('追加内容失败:', error);return;} console.log('内容追加成功');});// 删除文件 fs.unlink('output.txt',(error)=>{if(error){ console.error('删除文件失败:', error);return;} console.log('文件删除成功');});// 创建目录 fs.mkdir('newdir',{recursive:true},(error)=>{if(error){ console.error('创建目录失败:', error);return;} console.log('目录创建成功');});// 读取目录 fs.readdir('.',(error, files)=>{if(error){ console.error('读取目录失败:', error);return;} console.log('目录内容:', files);});// 检查文件是否存在 fs.access('input.txt', fs.constants.F_OK,(error)=>{if(error){ console.log('文件不存在');}else{ console.log('文件存在');}});// 获取文件信息 fs.stat('input.txt',(error, stats)=>{if(error){ console.error('获取文件信息失败:', error);return;} console.log('文件信息:', stats); console.log('是否为文件:', stats.isFile()); console.log('是否为目录:', stats.isDirectory()); console.log('文件大小:', stats.size);});// 路径操作const filePath = path.join(__dirname,'files','input.txt'); console.log('文件路径:', filePath); console.log('文件名:', path.basename(filePath)); console.log('目录名:', path.dirname(filePath)); console.log('扩展名:', path.extname(filePath)); console.log('绝对路径:', path.resolve(filePath));2. http(HTTP服务器)
const http =require('http');const url =require('url');const querystring =require('querystring');// 创建简单的HTTP服务器const server = http.createServer((req, res)=>{// 处理CORS res.setHeader('Access-Control-Allow-Origin','*'); res.setHeader('Access-Control-Allow-Methods','GET, POST, PUT, DELETE'); res.setHeader('Access-Control-Allow-Headers','Content-Type');// 处理OPTIONS请求if(req.method ==='OPTIONS'){ res.writeHead(200); res.end();return;}// 解析URLconst parsedUrl = url.parse(req.url,true);const pathname = parsedUrl.pathname;const query = parsedUrl.query; console.log(`收到请求: ${req.method}${pathname}`);// 路由处理if(pathname ==='/'){ res.writeHead(200,{'Content-Type':'text/html; charset=utf-8'}); res.end(` <!DOCTYPE html> <html> <head> <title>Node.js HTTP服务器</title> </head> <body> <h1>欢迎使用Node.js HTTP服务器</h1> <p>当前路径: ${pathname}</p> </body> </html> `);}elseif(pathname ==='/api/data'){// JSON响应const data ={message:'成功',data:[1,2,3,4,5]}; res.writeHead(200,{'Content-Type':'application/json; charset=utf-8'}); res.end(JSON.stringify(data));}else{// 404页面 res.writeHead(404,{'Content-Type':'text/html; charset=utf-8'}); res.end(` <!DOCTYPE html> <html> <head> <title>404 Not Found</title> </head> <body> <h1>404 - 页面不存在</h1> <p>您访问的页面不存在: ${pathname}</p> </body> </html> `);}}); server.listen(3000,()=>{ console.log('服务器运行在 http://localhost:3000/');});// 处理POST请求const server2 = http.createServer((req, res)=>{if(req.method ==='POST'&& req.url ==='/api/login'){let body =''; req.on('data',chunk=>{ body += chunk.toString();}); req.on('end',()=>{try{const data =JSON.parse(body); console.log('接收到的数据:', data); res.writeHead(200,{'Content-Type':'application/json; charset=utf-8'}); res.end(JSON.stringify({success:true,message:'登录成功',user: data }));}catch(error){ res.writeHead(400,{'Content-Type':'application/json; charset=utf-8'}); res.end(JSON.stringify({success:false,message:'数据格式错误'}));}});}else{ res.writeHead(404,{'Content-Type':'text/html; charset=utf-8'}); res.end('404 Not Found');}}); server2.listen(3001,()=>{ console.log('POST服务器运行在 http://localhost:3001/');});3. path(路径处理)
const path =require('path');// 路径拼接const fullPath = path.join(__dirname,'files','data.txt'); console.log('完整路径:', fullPath);// 路径解析const parsed = path.parse('/path/to/file.txt'); console.log('路径解析:', parsed);// {// root: '/',// dir: '/path/to',// base: 'file.txt',// ext: '.txt',// name: 'file'// }// 获取文件名 console.log('文件名:', path.basename('/path/to/file.txt'));// file.txt console.log('不含扩展名的文件名:', path.basename('/path/to/file.txt','.txt'));// file// 获取目录名 console.log('目录名:', path.dirname('/path/to/file.txt'));// /path/to// 获取扩展名 console.log('扩展名:', path.extname('/path/to/file.txt'));// .txt// 获取绝对路径 console.log('绝对路径:', path.resolve('files/data.txt'));// 规范化路径 console.log('规范化路径:', path.normalize('/path/to/../to/./file.txt'));// /path/to/file.txt// 判断是否为绝对路径 console.log('是否为绝对路径:', path.isAbsolute('/path/to/file.txt'));// true console.log('是否为绝对路径:', path.isAbsolute('files/data.txt'));// false// 获取当前工作目录 console.log('当前工作目录:', process.cwd()); console.log('__dirname:', __dirname); console.log('__filename:', __filename);4. events(事件处理)
const EventEmitter =require('events');// 创建事件发射器classMyEmitterextendsEventEmitter{}const myEmitter =newMyEmitter();// 监听事件 myEmitter.on('event',()=>{ console.log('事件触发!');});// 触发事件 myEmitter.emit('event');// 带参数的事件 myEmitter.on('data',(data)=>{ console.log('收到数据:', data);}); myEmitter.emit('data',{name:'张三',age:25});// 只触发一次的事件 myEmitter.once('once',()=>{ console.log('这个事件只会触发一次');}); myEmitter.emit('once'); myEmitter.emit('once');// 不会触发// 错误处理 myEmitter.on('error',(error)=>{ console.error('发生错误:', error.message);}); myEmitter.emit('error',newError('这是一个错误'));// 移除事件监听器consthandler=()=>{ console.log('这个监听器会被移除');}; myEmitter.on('remove', handler); myEmitter.emit('remove'); myEmitter.removeListener('remove', handler); myEmitter.emit('remove');// 不会触发// 获取监听器数量 console.log('监听器数量:', myEmitter.listenerCount('event'));// 获取所有监听器 console.log('所有监听器:', myEmitter.listeners('event'));5. stream(流处理)
const fs =require('fs');const{ Transform }=require('stream');// 读取流const readStream = fs.createReadStream('input.txt','utf8'); readStream.on('data',(chunk)=>{ console.log('读取数据块:', chunk);}); readStream.on('end',()=>{ console.log('读取完成');}); readStream.on('error',(error)=>{ console.error('读取错误:', error);});// 写入流const writeStream = fs.createWriteStream('output.txt','utf8'); writeStream.write('Hello, '); writeStream.write('Node.js!\n'); writeStream.end('结束写入'); writeStream.on('finish',()=>{ console.log('写入完成');}); writeStream.on('error',(error)=>{ console.error('写入错误:', error);});// 管道流(复制文件)const readStream2 = fs.createReadStream('input.txt');const writeStream2 = fs.createWriteStream('output.txt'); readStream2.pipe(writeStream2); readStream2.on('end',()=>{ console.log('文件复制完成');});// 转换流(大写转换)const upperCaseTransform =newTransform({transform(chunk, encoding, callback){this.push(chunk.toString().toUpperCase());callback();}}); fs.createReadStream('input.txt').pipe(upperCaseTransform).pipe(fs.createWriteStream('uppercase.txt'));// 压缩流const zlib =require('zlib');const gzip = zlib.createGzip(); fs.createReadStream('input.txt').pipe(gzip).pipe(fs.createWriteStream('input.txt.gz'));// 解压流const gunzip = zlib.createGunzip(); fs.createReadStream('input.txt.gz').pipe(gunzip).pipe(fs.createWriteStream('uncompressed.txt'));6. crypto(加密)
const crypto =require('crypto');// MD5哈希const hash1 = crypto.createHash('md5').update('Hello, Node.js!').digest('hex'); console.log('MD5:', hash1);// SHA256哈希const hash2 = crypto.createHash('sha256').update('Hello, Node.js!').digest('hex'); console.log('SHA256:', hash2);// HMACconst hmac = crypto.createHmac('sha256','secret-key'); hmac.update('Hello, Node.js!'); console.log('HMAC:', hmac.digest('hex'));// AES加密const algorithm ='aes-256-cbc';const key = crypto.randomBytes(32);const iv = crypto.randomBytes(16);functionencrypt(text){const cipher = crypto.createCipheriv(algorithm, key, iv);let encrypted = cipher.update(text,'utf8','hex'); encrypted += cipher.final('hex');return encrypted;}functiondecrypt(encrypted){const decipher = crypto.createDecipheriv(algorithm, key, iv);let decrypted = decipher.update(encrypted,'hex','utf8'); decrypted += decipher.final('utf8');return decrypted;}const original ='Hello, Node.js!';const encrypted =encrypt(original);const decrypted =decrypt(encrypted); console.log('原文:', original); console.log('加密:', encrypted); console.log('解密:', decrypted);// 生成随机数const randomBytes = crypto.randomBytes(16).toString('hex'); console.log('随机数:', randomBytes);// 生成UUIDfunctiongenerateUUID(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){const r = Math.random()*16|0;const v = c ==='x'? r :(r &0x3|0x8);return v.toString(16);});} console.log('UUID:',generateUUID());四、NPM包管理
4.1 NPM基础命令
# 初始化项目npm init # 初始化项目(使用默认配置)npm init -y# 安装依赖npminstall# 安装指定包npminstall package-name # 安装到生产环境npminstall package-name --save# 安装到开发环境npminstall package-name --save-dev # 全局安装npminstall-g package-name # 卸载包npm uninstall package-name # 更新包npm update package-name # 查看已安装的包npm list # 查看全局安装的包npm list -g# 查看包信息npm info package-name # 搜索包npm search package-name # 查看过期的包npm outdated # 清理缓存npm cache clean --force4.2 package.json详解
{"name":"my-nodejs-app","version":"1.0.0","description":"我的Node.js应用","main":"index.js","scripts":{"start":"node index.js","dev":"nodemon index.js","test":"jest","build":"webpack --mode production"},"dependencies":{"express":"^4.18.2","mongoose":"^7.0.3","dotenv":"^16.0.3"},"devDependencies":{"nodemon":"^2.0.22","jest":"^29.5.0","webpack":"^5.82.1"},"engines":{"node":">=16.0.0","npm":">=8.0.0"},"author":"张三","license":"MIT","repository":{"type":"git","url":"https://github.com/username/repo.git"},"keywords":["nodejs","express","api"]}4.3 常用NPM包
express - Web框架
npminstall express const express =require('express');const app =express();// 中间件 app.use(express.json()); app.use(express.urlencoded({extended:true}));// 路由 app.get('/',(req, res)=>{ res.send('Hello, Express!');}); app.get('/api/users',(req, res)=>{ res.json([{id:1,name:'张三'},{id:2,name:'李四'}]);}); app.post('/api/users',(req, res)=>{const user = req.body; user.id = Date.now(); res.status(201).json(user);}); app.listen(3000,()=>{ console.log('Express服务器运行在 http://localhost:3000');});nodemon - 自动重启
npminstall-D nodemon {"scripts":{"dev":"nodemon index.js"}}dotenv - 环境变量
npminstall dotenv // .envPORT=3000DB_HOST=localhost DB_USER=root DB_PASS=password // index.jsrequire('dotenv').config();const port = process.env.PORT||3000; console.log('服务器端口:', port);axios - HTTP客户端
npminstall axios const axios =require('axios');// GET请求asyncfunctiongetData(){try{const response =await axios.get('https://api.example.com/data'); console.log(response.data);}catch(error){ console.error('请求失败:', error);}}// POST请求asyncfunctionpostData(){try{const response =await axios.post('https://api.example.com/users',{name:'张三',age:25}); console.log(response.data);}catch(error){ console.error('请求失败:', error);}}mongoose - MongoDB ODM
npminstall mongoose const mongoose =require('mongoose');// 连接数据库 mongoose.connect('mongodb://localhost:27017/mydatabase',{useNewUrlParser:true,useUnifiedTopology:true});// 定义模型const UserSchema =newmongoose.Schema({name:{type: String,required:true},age:{type: Number,required:true},email:{type: String,required:true,unique:true}});const User = mongoose.model('User', UserSchema);// 创建用户asyncfunctioncreateUser(){const user =newUser({name:'张三',age:25,email:'[email protected]'});await user.save(); console.log('用户创建成功:', user);}// 查询用户asyncfunctionfindUsers(){const users =await User.find({age:{$gte:20}}); console.log('查询结果:', users);}// 更新用户asyncfunctionupdateUser(id){const user =await User.findByIdAndUpdate( id,{age:26},{new:true}); console.log('用户更新成功:', user);}// 删除用户asyncfunctiondeleteUser(id){await User.findByIdAndDelete(id); console.log('用户删除成功');}五、Express框架详解
5.1 Express基础
const express =require('express');const app =express();// 中间件配置 app.use(express.json()); app.use(express.urlencoded({extended:true})); app.use(express.static('public'));// 路由 app.get('/',(req, res)=>{ res.send('Hello, Express!');}); app.get('/about',(req, res)=>{ res.send('关于我们');});// 路由参数 app.get('/users/:id',(req, res)=>{const userId = req.params.id; res.send(`用户ID: ${userId}`);});// 查询参数 app.get('/search',(req, res)=>{const keyword = req.query.keyword; res.send(`搜索关键词: ${keyword}`);});// POST请求 app.post('/users',(req, res)=>{const user = req.body; res.status(201).json(user);});// 启动服务器 app.listen(3000,()=>{ console.log('服务器运行在 http://localhost:3000');});5.2 路由器
const express =require('express');const router = express.Router();// 用户路由 router.get('/',(req, res)=>{ res.json([{id:1,name:'张三'},{id:2,name:'李四'}]);}); router.get('/:id',(req, res)=>{const userId = req.params.id; res.json({id: userId,name:'张三'});}); router.post('/',(req, res)=>{const user = req.body; user.id = Date.now(); res.status(201).json(user);}); router.put('/:id',(req, res)=>{const userId = req.params.id;const user = req.body; res.json({id: userId,...user });}); router.delete('/:id',(req, res)=>{const userId = req.params.id; res.json({message:`用户${userId}已删除`});}); module.exports = router;// 在主应用中使用const userRoutes =require('./routes/users'); app.use('/api/users', userRoutes);5.3 中间件
// 日志中间件constlogger=(req, res, next)=>{ console.log(`${newDate().toISOString()} - ${req.method}${req.url}`);next();};// 认证中间件constauth=(req, res, next)=>{const token = req.headers.authorization;if(!token){return res.status(401).json({message:'未授权'});}// 验证tokennext();};// 错误处理中间件consterrorHandler=(err, req, res, next)=>{ console.error(err.stack); res.status(500).json({message:'服务器错误'});};// 使用中间件 app.use(logger); app.use('/api/protected', auth);// 错误处理 app.use(errorHandler);5.4 模板引擎
// 安装EJS npm install ejs // 配置模板引擎 app.set('view engine','ejs'); app.set('views','./views');// 渲染模板 app.get('/',(req, res)=>{ res.render('index',{title:'我的网站',users:[{name:'张三',age:25},{name:'李四',age:30}]});});// views/index.ejs<!DOCTYPE html><html><head><title><%= title %></title></head><body><h1><%= title %></h1><ul><% users.forEach(user=>{%><li><%= user.name %>-<%= user.age %>岁</li><%})%></ul></body></html>六、数据库操作
6.1 MongoDB
const mongoose =require('mongoose');// 连接数据库 mongoose.connect('mongodb://localhost:27017/myapp');// 定义Schemaconst userSchema =newmongoose.Schema({name:{type: String,required:true},email:{type: String,required:true,unique:true},age:{type: Number,default:0},createdAt:{type: Date,default: Date.now }});// 创建Modelconst User = mongoose.model('User', userSchema);// CRUD操作// 创建asyncfunctioncreateUser(){const user =newUser({name:'张三',email:'[email protected]',age:25});await user.save();return user;}// 读取asyncfunctiongetUsers(){const users =await User.find({age:{$gte:18}}).sort({age:-1}).limit(10);return users;}asyncfunctiongetUserById(id){const user =await User.findById(id);return user;}// 更新asyncfunctionupdateUser(id, updates){const user =await User.findByIdAndUpdate( id, updates,{new:true,runValidators:true});return user;}// 删除asyncfunctiondeleteUser(id){await User.findByIdAndDelete(id);}6.2 MySQL
const mysql =require('mysql2/promise');// 创建连接池const pool = mysql.createPool({host:'localhost',user:'root',password:'password',database:'myapp',waitForConnections:true,connectionLimit:10,queueLimit:0});// CRUD操作// 创建asyncfunctioncreateUser(user){const sql ='INSERT INTO users (name, email, age) VALUES (?, ?, ?)';const[result]=await pool.execute(sql,[user.name, user.email, user.age]);return result.insertId;}// 读取asyncfunctiongetUsers(){const[rows]=await pool.execute('SELECT * FROM users WHERE age >= ?',[18]);return rows;}asyncfunctiongetUserById(id){const[rows]=await pool.execute('SELECT * FROM users WHERE id = ?',[id]);return rows[0];}// 更新asyncfunctionupdateUser(id, updates){const sql ='UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?';const[result]=await pool.execute(sql,[updates.name, updates.email, updates.age, id]);return result.affectedRows >0;}// 删除asyncfunctiondeleteUser(id){const sql ='DELETE FROM users WHERE id = ?';const[result]=await pool.execute(sql,[id]);return result.affectedRows >0;}// 事务asyncfunctiontransferMoney(fromId, toId, amount){const connection =await pool.getConnection();try{await connection.beginTransaction();// 扣款await connection.execute('UPDATE users SET balance = balance - ? WHERE id = ?',[amount, fromId]);// 加款await connection.execute('UPDATE users SET balance = balance + ? WHERE id = ?',[amount, toId]);await connection.commit();returntrue;}catch(error){await connection.rollback();throw error;}finally{ connection.release();}}七、实战项目:RESTful API
7.1 项目结构
my-api/ ├── config/ │ └── database.js ├── models/ │ └── User.js ├── routes/ │ └── users.js ├── middleware/ │ └── auth.js ├── controllers/ │ └── userController.js ├── app.js ├── .env └── package.json 7.2 实现代码
// package.json{"name":"my-api","version":"1.0.0","main":"app.js","scripts":{"start":"node app.js","dev":"nodemon app.js"},"dependencies":{"express":"^4.18.2","mongoose":"^7.0.3","dotenv":"^16.0.3","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.0"},"devDependencies":{"nodemon":"^2.0.22"}}// .envPORT=3000MONGODB_URI=mongodb://localhost:27017/myapi JWT_SECRET=your-secret-key // config/database.jsconst mongoose =require('mongoose');constconnectDB=async()=>{try{await mongoose.connect(process.env.MONGODB_URI); console.log('MongoDB连接成功');}catch(error){ console.error('MongoDB连接失败:', error); process.exit(1);}}; module.exports = connectDB;// models/User.jsconst mongoose =require('mongoose');const bcrypt =require('bcryptjs');const userSchema =newmongoose.Schema({name:{type: String,required:true,trim:true},email:{type: String,required:true,unique:true,trim:true,lowercase:true},password:{type: String,required:true,minlength:6},role:{type: String,enum:['user','admin'],default:'user'}},{timestamps:true});// 密码加密 userSchema.pre('save',asyncfunction(next){if(!this.isModified('password')){returnnext();}const salt =await bcrypt.genSalt(10);this.password =await bcrypt.hash(this.password, salt);next();});// 密码验证 userSchema.methods.comparePassword=asyncfunction(candidatePassword){returnawait bcrypt.compare(candidatePassword,this.password);}; module.exports = mongoose.model('User', userSchema);// controllers/userController.jsconst User =require('../models/User');const jwt =require('jsonwebtoken');// 生成JWTconstgenerateToken=(userId)=>{return jwt.sign({ userId }, process.env.JWT_SECRET,{expiresIn:'7d'});};// 注册 exports.register=async(req, res)=>{try{const{ name, email, password }= req.body;// 检查用户是否已存在const existingUser =await User.findOne({ email });if(existingUser){return res.status(400).json({message:'邮箱已被注册'});}// 创建用户const user =newUser({ name, email, password });await user.save();// 生成tokenconst token =generateToken(user._id); res.status(201).json({message:'注册成功', token,user:{id: user._id,name: user.name,email: user.email,role: user.role }});}catch(error){ res.status(500).json({message:'服务器错误',error: error.message });}};// 登录 exports.login=async(req, res)=>{try{const{ email, password }= req.body;// 查找用户const user =await User.findOne({ email });if(!user){return res.status(401).json({message:'邮箱或密码错误'});}// 验证密码const isMatch =await user.comparePassword(password);if(!isMatch){return res.status(401).json({message:'邮箱或密码错误'});}// 生成tokenconst token =generateToken(user._id); res.json({message:'登录成功', token,user:{id: user._id,name: user.name,email: user.email,role: user.role }});}catch(error){ res.status(500).json({message:'服务器错误',error: error.message });}};// 获取用户信息 exports.getProfile=async(req, res)=>{try{const user =await User.findById(req.userId).select('-password');if(!user){return res.status(404).json({message:'用户不存在'});} res.json(user);}catch(error){ res.status(500).json({message:'服务器错误',error: error.message });}};// 更新用户信息 exports.updateProfile=async(req, res)=>{try{const{ name, email }= req.body;const user =await User.findByIdAndUpdate( req.userId,{ name, email },{new:true,runValidators:true}).select('-password');if(!user){return res.status(404).json({message:'用户不存在'});} res.json({message:'更新成功', user });}catch(error){ res.status(500).json({message:'服务器错误',error: error.message });}};// middleware/auth.jsconst jwt =require('jsonwebtoken');constauth=async(req, res, next)=>{try{const token = req.header('Authorization')?.replace('Bearer ','');if(!token){return res.status(401).json({message:'未提供认证token'});}const decoded = jwt.verify(token, process.env.JWT_SECRET); req.userId = decoded.userId;next();}catch(error){ res.status(401).json({message:'无效的token'});}}; module.exports = auth;// routes/users.jsconst express =require('express');const router = express.Router();const{ register, login, getProfile, updateProfile }=require('../controllers/userController');const auth =require('../middleware/auth'); router.post('/register', register); router.post('/login', login); router.get('/profile', auth, getProfile); router.put('/profile', auth, updateProfile); module.exports = router;// app.jsrequire('dotenv').config();const express =require('express');const connectDB =require('./config/database');const userRoutes =require('./routes/users');const app =express();// 连接数据库connectDB();// 中间件 app.use(express.json()); app.use(express.urlencoded({extended:true}));// 路由 app.use('/api/users', userRoutes);// 错误处理 app.use((err, req, res, next)=>{ console.error(err.stack); res.status(500).json({message:'服务器错误'});});// 启动服务器constPORT= process.env.PORT||3000; app.listen(PORT,()=>{ console.log(`服务器运行在 http://localhost:${PORT}`);});八、Node.js最佳实践
8.1 错误处理
// 使用异步函数和try-catchasyncfunctionasyncOperation(){try{const result =awaitsomeAsyncOperation();return result;}catch(error){ console.error('操作失败:', error);throw error;}}// 错误处理中间件 app.use((err, req, res, next)=>{ console.error(err.stack); res.status(500).json({message:'服务器错误',error: process.env.NODE_ENV==='development'? err.message :{}});});// 未捕获的Promise拒绝 process.on('unhandledRejection',(reason, promise)=>{ console.error('未处理的Promise拒绝:', reason);});// 未捕获的异常 process.on('uncaughtException',(error)=>{ console.error('未捕获的异常:', error); process.exit(1);});8.2 安全性
// 使用helmetconst helmet =require('helmet'); app.use(helmet());// 使用corsconst cors =require('cors'); app.use(cors());// 输入验证const{ body, validationResult }=require('express-validator'); app.post('/users',[body('name').notEmpty().withMessage('姓名不能为空'),body('email').isEmail().withMessage('邮箱格式不正确'),body('password').isLength({min:6}).withMessage('密码至少6位')],(req, res)=>{const errors =validationResult(req);if(!errors.isEmpty()){return res.status(400).json({errors: errors.array()});}// 处理请求});// 环境变量require('dotenv').config();// 限制请求频率const rateLimit =require('express-rate-limit');const limiter =rateLimit({windowMs:15*60*1000,// 15分钟max:100// 限制100次请求}); app.use(limiter);8.3 性能优化
// 启用Gzip压缩const compression =require('compression'); app.use(compression());// 使用缓存const NodeCache =require('node-cache');const cache =newNodeCache({stdTTL:600});// 10分钟缓存 app.get('/api/data',async(req, res)=>{const cacheKey ='data';const cachedData = cache.get(cacheKey);if(cachedData){return res.json(cachedData);}const data =awaitfetchData(); cache.set(cacheKey, data); res.json(data);});// 使用连接池const pool = mysql.createPool({connectionLimit:10,// 其他配置});// 使用集群(多进程)const cluster =require('cluster');const numCPUs =require('os').cpus().length;if(cluster.isMaster){for(let i =0; i < numCPUs; i++){ cluster.fork();}}else{// 启动服务器 app.listen(3000);}8.4 日志记录
const winston =require('winston');const logger = winston.createLogger({level:'info',format: winston.format.combine( winston.format.timestamp(), winston.format.json()),transports:[newwinston.transports.File({filename:'error.log',level:'error'}),newwinston.transports.File({filename:'combined.log'})]});if(process.env.NODE_ENV!=='production'){ logger.add(newwinston.transports.Console({format: winston.format.simple()}));}// 使用日志 logger.info('服务器启动'); logger.error('发生错误', error);九、部署和运维
9.1 PM2进程管理
# 安装PM2npminstall-g pm2 # 启动应用 pm2 start app.js # 启动应用并指定名称 pm2 start app.js --name my-app # 列出所有进程 pm2 list # 查看日志 pm2 logs # 重启应用 pm2 restart my-app # 停止应用 pm2 stop my-app # 删除应用 pm2 delete my-app # 监控 pm2 monit # 保存进程列表 pm2 save # 设置开机自启 pm2 startup 9.2 Docker容器化
# Dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . EXPOSE 3000 CMD ["node", "app.js"] # 构建镜像docker build -t my-nodejs-app .# 运行容器docker run -p3000:3000 my-nodejs-app # docker-compose.ymlversion:'3'services:app:build: . ports:-"3000:3000"environment:- NODE_ENV=production - MONGODB_URI=mongodb://mongo:27017/myapp depends_on:- mongo mongo:image: mongo:latest ports:-"27017:27017"volumes:- mongo-data:/data/db volumes:mongo-data:# 使用docker-compose启动docker-compose up -d十、总结
Node.js是一个功能强大、生态丰富的后端开发平台。通过本文的学习,你应该掌握了:
- Node.js的基础概念和安装配置
- 模块系统(CommonJS和ES6)
- 核心模块的使用(fs、http、path、events等)
- NPM包管理和常用包
- Express框架的详细使用
- 数据库操作(MongoDB、MySQL)
- 完整的RESTful API项目实战
- Node.js最佳实践(错误处理、安全性、性能优化)
- 部署和运维(PM2、Docker)
学习建议:
- 多动手实践,创建自己的项目
- 深入理解Node.js的异步编程模型
- 学习TypeScript提高代码质量
- 关注Node.js的最新发展
- 参与开源项目,学习优秀的代码
- 掌握监控和调试技巧
Node.js的学习是一个持续的过程,随着技术的发展,Node.js也在不断进化。保持学习的热情,不断提升自己的技能,你一定能成为一名优秀的Node.js开发者!
希望这篇Node.js详解教程对你有所帮助!如果你有任何问题或建议,欢迎留言讨论。持续学习,不断进步,让我们一起在后端开发的道路上越走越远!