「本文正在参与技术专题征文Node.js进阶之路,点击查看详情」
前言
大家好!我是前端乾阳,本人从去年11月份开始学习nodejs,对于nodejs的强大感受非常深刻,今天发现了一个很有意思的话题,2022nodejs真的凉了吗?我觉得只有真的上手接触了,你才有一个自己的答案。话不多说来了
创建一个node项目
首先你得先有一个node环境,这个我相信大家都有了,如果没有就去官网下载,这里不做赘述。 创建一个文件夹 koa-node 进入文件夹后打开终端执行
npm init -y
npm 官方提供的 npm init 命令帮助我们快速初始化 package.json 文件。
之后就会看见生成了一个package.json文件 每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。
开发环境配置
众所周知node.js是一个基于ChromeV8引擎的javaScript运行环境。所以安装了node之后,我们可以在终端node xxx.js运行我们的js文件,即赋予了javaScript脱离浏览器之外的运行能力。但是如果开发的时候每次都通过node xx.js去运行你的文件,非常影响我们的开发效率,所以我们可以通过nodemon这个工具来解决这个问题。
nodemon可以自动检测到目录中的文件更改时通过重新启动应用程序来调试基于node.js的应用程序。在当前目录下打开终端执行
npm install nodemon
顺道安装一下我们的主角koa
npm install koa
接着创建文件src/main.js
const Koa = require('koa')
const app = new Koa();
app.use((ctx,next) => {
ctx.body = 'hello QianYang'
})
app.listen(3000,()=>{
console.log('我们启动了一个3000端口的服务')
})
上面的代码是一个简单的koa服务示例待会用来测试我们的koa是否安装成功,接着配置好nodemon,打开package.json
{
"name": "juejin-node",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon ./src/main.js",//加入nodemon配置
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.13.4",
"nodemon": "^2.0.15"
}
}
配置完成后在终端执行
npm run dev
成功如图所示
在浏览器输入 http://localhost:3000
好了我们的koa+node的项目搭建就完成了,再见!
开发实战配置
刚才只是一个实例项目,在我们实际开发中肯定没有这么简单,但是很多同学都接触不到node类型项目的开发,即便入门了也非常的迷漫,真实的一个node项目的搭建是怎么样的。这个问题当时也困扰了我非常久,但是我通过一些视频网站和github上面的一些node项目找到了一些答案当然未必是最好的,但是我用着感觉还行我就分享一下。
- app-管理引入的依赖与中间件
- config-项目的一些配置文件
- error-项目的错误处理
- controller-控制器就是控制最终返回数据的
- middleware-中间件管理
- router-项目接口路由文件
- service-数据库查询
- utils-工具管理
环境变量配置
首先我们先配置环境变量,在根目录下新建.env文件,输入
PORT=8080
也就是我们的端口号,随意什么都可以(不被占用就行) 接下来就是如何使用环境变量配置文件,我这里使用dotenv这个库来进行加载配置文件并使用。
npm install dotenv --save
安装完成后,创建src/config/config.dotenv.js文件
require('dotenv').config({path:'.env'})
module.exports = process.env
接着在main.js中导入
const {PORT} = require('./config/config.dotenv')//导入
const Koa = require('koa')
const app = new Koa();
app.use((ctx,next) => {
ctx.body = 'hello QianYang'
})
app.listen(PORT,()=>{
console.log(`启动的服务的端口是http://localhost:${PORT}`)
})
之后执行npm run dev
路由安装配置
我们接下来要安装koa的路由,koa不同于Express、nest等框架,它本身并没有集成很多应该有的东西,所以它的路由也是需要我们自己去安装的
npm i @koa/router
然后创建src/router/user.router.js文件
const Router = require('@koa/router');
const router = new Router({prefix:'/user'})//配置接口统一模块路径
router.get('/add',(ctx,next) => {
ctx.body = "路由配置成功了"
})
module.exports = router//导出路由
创建src/app/index.js文件,这里是的目的是把koa-router以及后续的全局中间件和第三方库引入聚合在这里集中管理。
const Koa = require('koa')
const app = new Koa()
const router = require('../router/user.router')
app.use(router.routes()).use(router.allowedMethods())//allowedMethods koa-router的中间件,用于处理请求方式不同进行特殊处理;
module.exports = app//导出
修改main.js,把main.js仅仅作为启动文件
const {PORT} = require('./config/config.dotenv')
const app = require('./app/index')//导入app文件
app.listen(PORT,()=>{
console.log(`启动的服务的端口是http://localhost:${PORT}`)
})
启动项目npm run dev
在浏览器url输入http://localhost:8080/user/add
路由的配置基本是成功了,但是如果我们有很多个路由文件的话,分别引入,在分别挂载会有很多重复的代码,他会长这样
这里只是2个如果你有多个模块呢?不敢想象!所以我们要进行一个简单的优化
在router目录下新建
index.js文件
const fs = require('fs')//node.js文件处理模块
const Router = require('@koa/router')
const router = new Router();
//自动引入router
fs.readdirSync(__dirname).forEach(file => {
if (file !== 'index.js') {
let r = require('./' + file)
router.use(r.routes())
}
})
module.exports = router
修改app/index.js
const Koa = require('koa')
const app = new Koa()
const router = require('../router/index')//修改
app.use(router.routes()).use(router.allowedMethods());
module.exports = app
为了验证是否成功,新建一个新的文件role.router.js
const Router = require('@koa/router');
const router = new Router({prefix:'/role'})//配置接口统一模块路径
router.get('/add',(ctx,next) => {
ctx.body = "路由自动引入成功了"
})
module.exports = router
npm run dev启动项目
连接数据库
一个真正的后端程序数据库是必不可少的,为此我还专门看了mysql必知必会等书籍,所以这里数据库我选择的是mysql,因为我认为这个是比较偏大众一点的数据库还是学一下比较好。既然选择了mysql作为我们这个项目的数据库,我们首先要先安装一下数据库,安装很简单自行找一下就好了。 安装成功后,我们就要选择一个快速操作数据库的ORM工具,目前市面上基于node的ORM工具有不少例如TypeORM, Sequelize等。这里我选择的是Sequelize。 我们安装官网走一下安装的流程
npm i sequelize
npm i mysql2
安装完成后,要连接到数据库,必须创建一个 Sequelize 实例. 这可以通过将连接参数分别传递到 Sequelize 构造函数或通过传递一个连接 URI 来完成:
//官方例子
const { Sequelize } = require('sequelize');
// 方法 1: 传递一个连接 URI
const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Postgres 示例
// 方法 2: 分别传递参数 (sqlite)
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'path/to/database.sqlite'
});
// 方法 3: 分别传递参数 (其它数据库)
const sequelize = new Sequelize('database', 'username', 'password', {//数据库名称,用户名,数据库密码
host: 'localhost',
dialect: /* 选择 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 *///你的数据库类型
});
所以我们新建一个文件src/db/sql.js
const {Sequelize} = require("sequelize")
const seq = new Sequelize("baobei","root","xxxx",
{
host:"localhost",
dialect: "mysql"
})//实例化,xxxx是你的数据库密码
打开你喜欢的数据库管理工具新建一个数据库
接下来就是调用Sequelize连接数据库,按照官方的文档
我们可以看出authenticate()返回的是一个Promise对象所以我们可以这样写
const {Sequelize} = require("sequelize");
const seq = new Sequelize("baobei",'root','root',{
host:"localhost",
dialect:"mysql"
})
seq.authenticate().then(() => {
console.log("和你的数据连接成功了!")
}).catch((err) => {
console.log("数据库连接失败",err)
})
module.exports = seq
打开终端执行
node src/db/sql.js
如图你的数据库就连接成功了
操作数据库模型编写接口
现在我们已经连接好了数据库,那么我们怎么去操作数据库呢?我们引入了Sequelize这么一个ORM模型的库,我可以通过ORM模型也就是对象映射的方式去操作数据库。
在 Sequelize 中可以用两种等效的方式定义模型:
定义模型后,可通过其模型名称在 sequelize.models 中使用该模型。
src/model/user.model.js
const {DataTypes} = require('sequelize')
const seq = require('../db/sql')
//username,password 为表的字段
const User = seq.define('baobei_user',{
username:{
type:DataTypes.STRING,//数据类型 更多数据类型参考https://www.sequelize.com.cn/core-concepts/model-basics#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B
allowNull:false,//列参数-是否能为空
unique:true,//列参数-创建唯一约束的简写 更多列参数可以参考https://www.sequelize.com.cn/core-concepts/model-basics#%E5%88%97%E5%8F%82%E6%95%B0
comment:'用户名,唯一'
},
password:{
type:DataTypes.CHAR(64),
allowNull: false,
comment: "用户密码"
}
})
User.sync()//模型同步
//- `User.sync()` - 如果表不存在,则创建该表(如果已经存在,则不执行任何操作)
//- `User.sync({ force: true })` - 将创建表,如果表已经存在,则将其首先删除
//- `User.sync({ alter: true })` - 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配.
module.exports = User
终端下执行
node src/model/user.model.js
这里我们可以清晰的看出实际上
Sequelize通过我们定义的这个模型对象帮我们执行了一个建表语句,也就是说我们的模型可以对应我们数据库里面的表。对应的我们也可以通过模型去操作我们表里的数据。
第一步 注册路由 编写 src/router/user.router.js
const Router = require('@koa/router');
const router = new Router({prefix:'/user'})//配置接口统一模块路径
router.get('/add',(ctx,next) => {
ctx.body = "路由配置成功了"
})
router.post('/register',(ctx,next) => {
ctx.body = {
code:"0",
message:"注册成功"
}
})
module.exports = router
使用api测试工具
看到返回值即注册成功
第二步 编写控制器
src/controller/user.controller.js文件 写入
class UserController {
async userRegister(ctx,next) {
try {//错误捕捉
ctx.body = {
code:0,
message:'注册成功',
result:"ok"
}
}catch (err) {
console.log(err)
}
}
}
module.exports = new UserController()
在src/router/user.router.js中引入
const {userRegister} = require('../controller/user.controller')
router.post('/register',userRegister)
测试是否成功
控制器通常是我们返回数据的最后一个步骤,所以这里的错误捕捉和错误反馈非常的重要,我们需要自己封装一下错误处理。
在
src/app/index.js注册一个错误响应事件
const Koa = require('koa')
const app = new Koa()
const router = require('../router/index')
app.use(router.routes()).use(router.allowedMethods());
app.on('error',(err,ctx) =>{//新增
let status = 500
switch(err.code){//这里可以判断你传进来的错误码,相应的改变返回的状态码,可以更好的知道发生了什么类型的错误,这里是一个例子,具体可以根据你前后端自己协调
case '10001':
status = 400
break
case '10002':
status = 409
break
default :
status = 500
}
ctx.status = status
ctx.body = err
})
module.exports = app
注册了error错误处理事件后,我们需要触发这个事件
新建src/error/error.js
module.exports = {
UserRegisterError:{
code:'10001',
message:'参数错误or其他错误',
result:''
}
}
在src/controller/user.controller.js中新增
const {UserRegisterError} = require("../error/error");
class UserController {
async userRegister(ctx,next) {
try {
console.log(dfsgsd)//没有这个变量所以打印这个变量会报错
}catch (err) {
console.log(err)
ctx.app.emit('error',UserRegisterError,ctx)//触发error事件
}
}
}
module.exports = new UserController()
测试
控制器的错误处理编写完成,要干正事了,接收post请求的参数,接收请求体的参数我们需要解析请求体,这就需要一个第三方库koa-body 安装
npm i koa-body
安装完成后在src/app/index.js中引入
const koaBody = require('koa-body')
app.use(koaBody())
在src/controller/user.controller.js中接收参数
const {UserRegisterError} = require("../error/error");
class UserController {
async userRegister(ctx,next) {
try {
const {username,password} = ctx.request.body
console.log(username,password)
ctx.body={
message:{
username,
password
}
}
}catch (err) {
console.log(err)
ctx.app.emit('error',UserRegisterError,ctx)
}
}
}
module.exports = new UserController()
测试
说明我们能解析并且拿到参数
const User = require('../model/user.model')//引入user模型
class UserService{
async createUser(username,password){
return await User.create({username,password})//创建数据库条目 更多查询器参考https://www.sequelize.com.cn/core-concepts/model-querying-basics
}
}
module.exports = new UserService()
在src/controller.user.controller.js中调用该方法并获取返回值
const {UserRegisterError} = require("../error/error");
const {createUser} = require('../service/user.service')
class UserController {
async userRegister(ctx,next) {
try {
const {username,password} = ctx.request.body
console.log(username,password)
const res = await createUser(username,password)//调用createUser方法传入参数
ctx.body={
code:0,
message:"注册成功",
result:res
}
}catch (err) {
console.log(err)
ctx.app.emit('error',UserRegisterError,ctx)
}
}
}
module.exports = new UserController()
测试
我们通过postman调用了接口后发现成功的返回值,并且查看数据库里面也有了这条新的数据,说明我们的接口编写成功了,是不是非常简单,前面感到麻烦其实是因为反复的没一步都进行了测试,如果是新手我希望每一步都进行测试,加深你的记忆并且知道你做这个环节你应该得到什么样的结果,防止一套写下来哪里报错了都不知道。
接下来我们可以举一反三快速写一条登录接口
登录接口编写
首先我们分析一下登录接口的需求
- 获取用户数据
- 用户密码进行比对,用户是否存在
- token
老规矩先注册路由
src/router/user.router.js
router.post('/login',(ctx,next) =>{
ctx.body = {
message:"登录成功"
}
})
测试同上这里就不放了
然后编写src/service/user.service.js
const User = require('../model/user.model')
class UserService{
async createUser(username,password){
return await User.create({username,password})
}
async getUserinfo({username,id}){
const whereOpt = {};//创建一个对象,这个对象就是包涵了需要查询的条件
id && Object.assign(whereOpt,{id})//合并对象,如果有这个参数的话就合并进去进行查询,可以复用查询
username && Object.assign(whereOpt,{username})
const res = await User.findOne({
attributes:['id','username','password','createdAt','updatedAt'],//查询成功后需要返回的字段
where:whereOpt//查询条件
})
return res ? res.dataValues : false//如果成功查询到的话就返回数据,如果没有的话就GG
}
}
module.exports = new UserService()
接下来编写src/controller/user.controller.js
const {UserRegisterError} = require("../error/error");
const {createUser,getUserinfo} = require('../service/user.service')
class UserController {
async userRegister(ctx,next) {
try {
const {username,password} = ctx.request.body
const res = await createUser(username,password)
ctx.body={
code:0,
message:"注册成功",
result:res
}
}catch (err) {
console.log(err)
ctx.app.emit('error',UserRegisterError,ctx)
}
}
async userLogin(ctx,next) {
try{
const {username} = ctx.request.body;
const {password,...res} = await getUserinfo({username})//把密码排除掉不返回
ctx.body = {
code:0,
message:"登录成功",
result:res
}
}catch (err) {
console.log(err)
}
}
}
module.exports = new UserController()
测试
这里我希望是用一个中间件的形式,因为检查密码这种问题是就可能多处使用的,写为中间件可以更好的复用代码
新建src/middleware/user.middleware.js编写中间件
const {getUserinfo} = require('../service/user.service')
const {userPasswordError, userIsundefined,userLoginError} = require("../error/error");
const verifyLogin = async (ctx,next) => {//验证密码中间件
try {
const {username,password} = ctx.request.body;
const res = await getUserinfo({username});
//用户不存在
if(!res) {
console.log('用户不存在',ctx.request.body)
ctx.app.emit('error',userIsundefined,ctx)
return
}
if (password!==res.password){
ctx.app.emit('error',userPasswordError,ctx)
return
}
}catch (err) {
console.error(err);
ctx.app.emit('error',userLoginError,ctx)
}
await next();//进入下一个中间件
}
module.exports= {
verifyLogin
}
在路由位置挂载中间件
const Router = require('@koa/router');
const {userRegister,userLogin} = require('../controller/user.controller')
const {verifyLogin} = require('../middleware/user.middleware')
const router = new Router({prefix:'/user'})//配置接口统一模块路径
router.get('/add',(ctx,next) => {
ctx.body = "路由配置成功了"
})
router.post('/register',userRegister)
router.post('/login',verifyLogin,userLogin)//注意中间件的顺序
module.exports = router
测试
错误测试
token是什么相信大家都知道的,这里直接教大家如何去使用jsonwebtoken生成token并且返回
安装
npm i jsonwebtoken
使用起来非常简单
const {createUser,getUserinfo} = require('../service/user.service')
const jwt = require('jsonwebtoken')//引入jwt
class UserController {
async userLogin(ctx,next) {
try{
const {username} = ctx.request.body;
const {password,...res} = await getUserinfo({username})
ctx.body = {
code:0,
message:"登录成功",
result:{
token:jwt.sign(res,'fjhtglxt',{expiresIn:'1d'})//jwt是加盐加密第二个参数就是盐,前面是我们的用户数据,expiresIn是token的有效时间
}
}
}catch (err) {
console.log(err)
}
}
}
module.exports = new UserController()
测试
可以看到这里我们已经成功的返回了token,既然有了token那么我的token验证怎么做呢。
也非常简单注册一个新的路由接口
router.get('/getingo')
然后新建一个公共中间件文件src/middleware/auth.middleware.js
const jwt = require('jsonwebtoken')
const {TokenExpiredError,JsonWebTokenError} = require('../error/error')
const auth = async (ctx,next) => {
try {
const {token} = ctx.request.header
ctx.state.user = jwt.verify(token,'fjhtglxt')//token验证,如果合法则存在变量ctx.state.user中
} catch (err) {
console.log(err)
switch (err.name) {
case 'TokenExpiredError':
console.error('token已经过期', err)
return ctx.app.emit('error', TokenExpiredError, ctx)
case 'JsonWebTokenError':
console.error('无效的token', err)
return ctx.app.emit('error', JsonWebTokenError, ctx)
}
}
await next()
// TokenExpiredError:{
// code:'405',
// message:'过期的token',
// result:''
// },
// JsonWebTokenError:{
// code:'405',
// message:'无效的token',
// result:''
// },
}
module.exports = {
auth
}
编写一个查询用户信息的方法src/controller/user.controller.js
async getInfo(ctx,next) {
try {
const {id} = ctx.state.user;//因为token里面加入了我们的个人信息所以我们可以解构出我们的id
const res = await getUserinfo({id})
ctx.body = {
code:0,
message:'查询成功',
result:res
}
}catch (err) {
console.log(err)
}
}
在路由挂载中间件
const Router = require('@koa/router');
const {userRegister,userLogin,getInfo} = require('../controller/user.controller')
const {verifyLogin} = require('../middleware/user.middleware')
const {auth} = require("../middleware/auth.middleware");
const router = new Router({prefix:'/user'})//配置接口统一模块路径
router.get('/add',(ctx,next) => {
ctx.body = "路由配置成功了"
})
router.post('/register',userRegister)
router.post('/login',verifyLogin,userLogin)
router.get('/getinfo',auth,getInfo)//token验证中间件,获取个人信息控制器
module.exports = router
测试
这个查询个人信息的接口只是为了演示如何验证token,实际上并没有什么用。。。
扩展
刚才我们频繁的创建文件,而且为了清晰文件名还非常的长,我相信正常人都开始烦了,我之前就觉得这样非常容易出错,有可能哪个文件打错字就出问题了,但是我在掘金看到了一篇非常nice的文章@iel手把手带你开发一个脚手架(上)之后又想到了nest的命令行创建文件,但是我又不喜欢用nest,所以我尝试放到我的项目里。
npm i -D commander inquirer fs-extra kolorist
- commander 插件提供命令注册、参数解析、执行回调
- inquirer 插件用于命令行的交互(问答)
- fs-extra 插件是对
nodejs文件Api的进一步封装,便于使用 - kolorist 插件用于输出颜色信息进行友好提示 这4个依赖组合起来 思路就非常的清晰了,注册命令,然后构建命令的交互,通过fs-extra创建文件,让命令变好看
第一步我们先来注册命令
新建src/index.js
#!/usr/bin/env node //#!/usr/bin/env node
const { Command } = require("commander")
const {onCreate} = require("./commands/create")//我们执行命令后需要回调的事件
//创建命令对象
const program = new Command()
//注册命令、参数、回调
//注册create命令
program.command('create')
//添加命令描述
.description('创建一个模块文件')
// 添加命令参数 -t | --type <type> ,<type> 表示该参数必填,[type] 表示选填
.option('-t --type <type>',`创建类型,可选值:model, router, service, middleware, controller`)
// 注册命令回调
.action(onCreate)//回调事件
// 执行命令行参数解析
program.parse()
//npm run cli -- -t all
新建src/commands/create.js
这个文件里面我们需要编写我们的命令界面的交互行为。
const inquirer = require("inquirer")
const {red} = require("kolorist")
const CREATE_TYPES=['model',"service","router","middleware","controller","all"]
const DoesItExist = require("./createModel")//文件创建的方法,因为写在里面太乱了,我抽离出来单独维护
async function onCreate(cmd) {
console.log(cmd)
let {type} = cmd
//如果没有在命令参数里面带入type那么就询问一次
if(!type) {
console.log(type)
const result = await inquirer.prompt([
{
//用于获取后的属性名
name:'type',
//交互方式为列表单选
type:"list",
//提示信息
message:'(必填) 请选择创建类型:',
//选项列表
choices:CREATE_TYPES,
//默认值,这里是下标索引
default:0
}
])
type = result.type
}
//如果获取的类型不在我们的支持范围内,那么输出错误提示并且重新选择
if(CREATE_TYPES.every((t) => type !== t)){
console.log(
red(`当前类型仅支持:${CREATE_TYPES.join(', ')},收到不在支持范围内的 "${type}",请重新选择!`)
)
return onCreate()
}
try {
switch (type) {
case 'model'|'service'|'router'|'middleware'|'controller':
const info = await inquirer.prompt([
{
name:'name',
type:'input',
message:`(必填) 请选择创建${type}的名称:`,
validate: (value) => {
if (value.trim() === '') {
return '组件 name 是必填项!'
}
return true
}
}
])
await DoesItExist(info.name,type)
break
case 'all'://如果是all则在配置的文件夹都创建文件
const all = await inquirer.prompt([
{
name:'name',
type:'input',
message:`(必填) 请选择创建${type}的名称:`,
validate: (value) => {
if (value.trim() === '') {
return '组件 name 是必填项!'
}
return true
}
}
])
//创建model文件
const createModel = DoesItExist(all.name,"model")
//创建service文件
const createService = DoesItExist(all.name,"service")
//创建router文件
const createRouter = DoesItExist(all.name,"router")
//创建middleware文件
const createMiddleware = DoesItExist(all.name,"middleware")
//创建controller文件
const createController = DoesItExist(all.name,"controller")
await Promise.all([createModel,createService,createRouter,createMiddleware,createController])
break
default:
break
}
} catch (e){
console.error(e)
}
}
module.exports={
onCreate
}
接下来编写我们的创建文件的方法,新建src/commands/createModel.js
const fs = require('fs-extra')
const {blue, red} = require("kolorist")
async function DoesItExist (name,type){
const exists = await fs.pathExists(`./src/${type}/${name}.${type}.js`)
if (!exists){//检查文件是否存在
createModel(name,type)
} else {
console.log(red("文件已经存在"))
}
}
function createModel(name,type) {
fs.ensureFile(`./src/${type}/${name}.${type}.js`,(err) => {
if(err){
throw err
} else {
console.log(blue(`./src/${type}/${name}.${type}.js,创建完成`))
}
})
}
module.exports = DoesItExist
完成了之后,我们需要去配置我们的命令,在package.json中“scripts”中添加
"cli": "node ./src/index.js create"
接下来可以测试一下命令
npm run cli -- -t all
输入创建文件夹的名称,比如 role
可以看到在终端这里我们是成功了的,因为我之前创建了一个role.router.js文件所以他会说文件已经存在但是其他没有这个文件的文件夹已经创建成功了,我们可以刷新一下文件夹看一下。
结束
这是我第二次发文章,内容有点多,我可能也没写的非常的详细,但是我相信如果按照文章一步步走下来一定能跑通的,当然如果要上生产环境的项目肯定没有这么简单,还会有日志,pm2,部署之类的东西。这些东西在node生态里面有很多选择,根本不难,大家可以自己去研究。 相应的node给了js脱离浏览器的能力,它也有很多其他的玩法,不局限于后端,还有serverless之类的东西。
另外本文的所有代码均上传gitee
写文真的不容易,希望手留余香