Node.js--》Simple fund management system backend project practice (backend)

        Get started today node + vue3 +):Techniques that can be learned by reading this article. I will open source the project code to my GithHub in the last article of the two columns of front-end and back-end. , you can download and run it by yourself. I hope this article will be helpful to friends who can pay more attention to this column and learn more front-end node knowledge. Then, at the beginning, I will briefly introduce the aspects of the technology stack used in this project. (Front-end linkBuild a front-end and back-end separation project of a simple fund management system. Because the front-end and back-end are separated, there will be two columns to explain the implementation of the front-end and the back-end respectively. The back-end project For article explanation, please refer to: ts

node: An open source, cross-platform JavaScript running environment based on the Chrome V8 engine.

express: A web application framework based on node.js that helps developers quickly build reliable and efficient web applications.

pm2: A production environment process management tool for node applications, which can help simplify application deployment, operation and monitoring.

MongoDB: A document-oriented NoSQL database system that can flexibly store unstructured data.

Apifox: Easy-to-use interface and features to help developers and teams create, design, and manage RESTful APIs more efficiently.

jwt: Used to define a secure, verifiable and trustworthy way for information transmitted over the network to be used for authentication and authorization mechanisms.

Table of contents

express build server

Connect to MongoDB database

Build registration interface

Build login interface

Get login information

Add and get information

Edit and delete information

Upload warehouse


Note: The back-end code is still based on the source code. After I finished writing the back-end code, I then wrote the front-end code and found that the back-end code still had some minor flaws, so I did a little Please note that the details of the code explained in this article may be different from the code given in the source code! ! !

express build server

First we need to create a folder to store the back-end project, and then drag the folder into the editor vscode. Next, we need to open the terminal and execute npm init Initialize the package.json file. The relevant steps are as follows:

Next, start loading the express framework and execute the following command in the terminal:

npm install express

Next we create the entry file server.js in the project folder and start using the express framework:

const express  = require("express")
const app = express()
// 设置路由
app.get('/', (req,res) =>{
  res.send('hello world')
})
// 设置端口号
const post = process.env.PORT || 8080
app.listen(post, () => {
  console.log(`Server is running on port ${post},url: http://127.0.0.1:${post}`)
})

In order to facilitate the execution of the code, here we use the pm2 process management tool. You can refer to the installation and specific usage tutorials of pm2. My previous article: In-depth understanding of PM2: Node.js application deployment and management tool , for the convenience of introduction, here we can directly in the package.json file Set up our command as follows:

The pm2 process management tool will not stop running when you close the terminal. If it is the first time to run the project, you can execute it according to the command we set above: npm run start. If you want to close the project, execute pm2 Just add the project name to stop. If you want to view the log in real time, just execute pm2 log. You will experience the specific operation by yourself, so I won’t go into details here.

Next, we open our local 5000 port, and we can see the projects running on our backend:

Connect to MongoDB database

Next, we create a MongoDB database locally to use it to store the data needed to write the corresponding interface in the backend. If you don’t know MongoDB, you can refer to my previous article:MongoDB database, then we need to start executing node to connect to the MongoDB database.

Start MongoDB service:

Click the win key and enter cmd, click Run as administrator

Execute net start MongoDB command to run the MongoDB database service:

Create MongoDB database:

Use the Navicat graphical management tool to create a database. Click New Connection and select MongoDB

Configure the corresponding parameters, and the connection can be made by default. Under normal circumstances, we only need to connect to the main database to check data, so we can choose an independent connection method. After filling in the general parameters, click to test whether the connection is normal, and you can connect! Because it is a personal test, I use the local localhost:

The following interface appears, indicating that the connection is successful. We can just click OK.​ 

Note: After entering the database, we delete the default database and re-create a database named node_fund

Connect to MongoDB database:

Next we need to use the vscode tool to connect to the mongodb database. First, execute the following command on the terminal to install the corresponding package:

npm install mongoose

After the installation is complete, we execute the following command in server.js to verify whether the database connection is successful:

// 引入 express 服务框架
const express  = require("express")
// 引入 mongoose 数据库服务
const mongoose = require("mongoose")
const app = express()
// 连接数据库的 URL
const MongoUrl = 'mongodb://localhost:27017/node_fund'
// 连接数据库
mongoose.connect(MongoUrl).then(() => {
  console.log('连接成功')
}).catch((err) => {
  console.log('连接失败', err)
})
// 设置路由
app.get('/', (req,res) =>{
  res.send('hello world')
})
// 设置端口号
const post = process.env.PORT || 5000
app.listen(post, () => {
  console.log(`Server is running on port ${post},url: http://127.0.0.1:${post}`)
})

When we execute pm2 log to view the log, we can see that our terminal prints the words successful connection:

When we change the default URL of MongoUrl, the terminal will print out the corresponding connection failure and corresponding error:

Build registration interface

Next we need to start building the real interface. First we need to create the relevant folder in the project root directory to write the interface:

Before writing the interface, we need to install body-parser first. In order to conveniently process the request body data of POST requests in node, so as to process and respond to data more easily, the terminal executes the following command to install:

npm install body-parser

After the installation is complete, we also need to use it in the entry file server.js to parse the request body:

const express  = require("express")
const bodyParser = require("body-parser")
const app = express()

// 使用 body-parser 中间件
app.use(bodyParser.urlencoded({ extended: false })) // 解析表单数据
app.use(bodyParser.json()) // 解析 JSON 格式的请求体

After the plug-in is installed, we also need to create a models file in the project root directory to store the data and corresponding types required to obtain data from the database. Here we need to use the Schema attribute:

const mongoose = require('mongoose')

// 定义模式,用于指定数据的结构和字段。
const Schema = mongoose.Schema
// 使用Schema变量来定义具体的数据模型
const userSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  avatar: {
    type: String,
  },
  identity: {
    type: String,
    required: true
  },
  date: {
    type: Date,
    default: Date.now
  },
})
/** 
 * 创建了一个名为users的MongoDB集合,并使用userSchema指定了集合中文档的结构
 * 将前一步创建的模型赋值给一个变量User,使其成为我们操作users集合的接口。
 */
module.exports = User = mongoose.model('users', userSchema)

After defining the required types of the corresponding registration login interface, you need to formally write the corresponding registration interface in the user.js file. The code is as follows:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()

// 引入具体的数据类型
const User = require('../../models/User')

/**
 * 注册接口
 * POST api/users/register 
 */
router.post('/register', (req,res) => {
  // 查询数据库中是否拥有邮箱
  User.findOne({ email: req.body.email }).then((user) => {
    if (user) {
      return res.status(400).json({ email: '邮箱已被注册' })
    } else {
      // 注册新邮箱
      const newUser = new User({
        name: req.body.name,
        email: req.body.email,
        avatar,
        password: req.body.password,
        identity: req.body.identity
      })
    }
  })
})

module.exports = router

Because passwords are crucial data, here we need to encrypt the user's password. The terminal executes the following command:

npm install bcrypt

After the download is successful, import bcrypt and perform corresponding hash encryption on the data. The modified code is as follows:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()
const bcrypt = require("bcrypt")

// 引入具体的数据类型
const User = require('../../models/User')

/**
 * 注册接口
 * POST api/users/register 
 */
router.post('/register', (req,res) => {
  // 查询数据库中是否拥有邮箱
  User.findOne({ email: req.body.email }).then((user) => {
    if (user) {
      return res.status(400).json({ email: '邮箱已被注册' })
    } else {
      // 注册新邮箱
      const newUser = new User({
        name: req.body.name,
        email: req.body.email,
        avatar,
        password: req.body.password,
        identity: req.body.identity
      })
      // 进行密码加密
      bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(newUser.password, salt, (err, hash) => {
          if (err) throw err
          newUser.password = hash
          newUser.save().then(user => res.json(user)).catch(err => console.log(err))
        })
      })
    }
  })
})

module.exports = router

After writing the corresponding code, we also need to introduce the routing code into the entry file server.js:

const express  = require("express")
const app = express()

// 引入user.js
const users = require('./routes/api/user')
// 使用routes
app.use('/api/users', users)

Next we need to use the interface testing tool Apifox to conduct corresponding interface testing. If you don’t know Apifox, you can refer to my previous article:Apifox: Detailed usage tutorial, with You can easily handle it. In order to facilitate testing, we first delete the avatar image parameter and test other parameters first. After we enter the relevant path and its parameters and click Send:

When we click Send again, it will appear that the current email address has been registered, which is in line with logical rules:

Next we need to process the avatar avatar parameters and save the image data to the database. We can use the Buffer object to process binary data and store it as a Buffer type field. Here we need to install the following plug-in for processing

npm install multer

Then the code is modified as follows:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()
const bcrypt = require("bcrypt")
const multer = require('multer');
const jwt = require("jsonwebtoken")
const passport = require("passport")

// 引入具体的数据类型
const User = require('../../models/User')

// 配置 multer
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'public/images') // 设置图片保存的路径
  },
  filename: function (req, file, cb) {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
    cb(null, file.fieldname + '-' + uniqueSuffix + '.' + file.mimetype.split('/')[1]) // 设置图片的文件名
  }
})
const upload = multer({ storage: storage })

/**
 * 注册接口
 * POST api/users/register 
 */
router.post('/register', upload.single('avatar'), (req,res) => {
  // 查询数据库中是否拥有邮箱
  User.findOne({ email: req.body.email }).then((user) => {
    if (user) {
      return res.status(400).json('邮箱已被注册')
    } else {
      const avatarUrl = req.protocol + '://' + req.get('host') + '/images/' + req.file.filename;
      // 注册新邮箱
      const newUser = new User({
        name: req.body.name,
        email: req.body.email,
        avatar: avatarUrl, // 使用上传的图片的文件名作为 avatar 字段的值
        password: req.body.password,
        identity: req.body.identity
      })
      // 进行密码加密
      bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(newUser.password, salt, (err, hash) => {
          if (err) throw err
          newUser.password = hash
          newUser.save().then(user => res.json(user)).catch(err => console.log(err))
        })
      })
    }
  })
})

After testing the Apifox interface next, the results obtained are as follows:

If we want to access image resources, here we also need to set the static resources to be accessible in the entry file:

app.use(express.static('public'));

When we open the Navicat visual management tool, find the corresponding data we created, and open the collection to see the data we generated in the testing tool Apifox, which is also presented in the database:

Build login interface

The same as the registration interface, the principle is that after we get the email and password requested by the user, we perform a query on the database. If the current database does not have the email data passed by the user, it will return that the user does not exist. Otherwise, we will start comparing passwords. Corresponding matches yield corresponding results:

/**
 * 登录接口
 * POST api/users/login
 */
router.post('/login', (req,res) => {
  const email = req.body.email
  const password = req.body.password
  // 查询数据库
  User.findOne({ email }).then(user => {
    if (!user) {
      return res.status(404).json({ email: '用户不存在!' })
    }
    // 密码匹配
    bcrypt.compare(password, user.password).then(isMatch => {
      if (isMatch) {
        res.json({ msg: 'success' })
      } else {
        return res.status(400).json({ password: '密码错误!' })
      }
    })
  })
})

For example, if we test the real data in the database, the result will definitely be success:

If we enter the password or user name casually, it may not be successful. For example, change the password:

Next, we need to return the corresponding token for successful login. The terminal executes the following command according to the corresponding package:

npm install jsonwebtoken

After importing const jwt = require("jsonwebtoken"), execute jwt to set a mark where the password matches. We set the expiration time for one hour, and then the token is spliced ​​with the previous string:

The corresponding results obtained when testing with the Apifox interface testing tool are as follows:

Get login information

After writing the login interface, we also need to write an interface function to obtain login information. First, to determine whether we have successfully logged in, we need to verify whether our current login token exists and is correct, so here we need to use tools such as passport-jwt to do it:

npm install passport-jwt passport

Next we need to initialize passport in the entry file server.js, and then extract the logic code separately:

// passport初始化
app.use(passport.initialize())
// 引入passport逻辑功能代码
require("./config/passport")(passport)

In the passport file under the extracted config folder, here we start to write the real token verification:

const JwtStrategy = require("passport-jwt").Strategy
ExtractJwt = require("passport-jwt").ExtractJwt
const mongoose = require("mongoose")
const User = mongoose.model("users")

const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken()
opts.secretOrKey = 'secret'

module.exports = passport => {
  passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
    User.findById(jwt_payload.id).then(user => {
      if (user) {
        return done(null, user)
      }
      return done(null, false)
    }).catch((err) => {
      console.log(err)
    })
  }))
}

In the user.js routing folder, we begin to write the interface for obtaining login information. Here we still splice the image resources to the corresponding paths:

/**
 * 获取登录信息
 * POST api/users/current
 */
router.get('/current', passport.authenticate("jwt", { session: false }), (req,res) => {
  res.json({
    id: req.user.id,
    name: req.user.name,
    email: req.user.email,
    avatar: req.user.avatar,
    identity: req.user.identity
  })
})

The results obtained through Apifox are as follows:

Add and get information

After the registration and login functions are written, we need to start writing the interface for adding and obtaining information. Here we need to create a new profile file under the api folder, and a new Profile file under the models folder:

The two files created must be introduced in the entry file server.js:

// 引入profile.js
const profile = require('./routes/api/profile')
// 使用routes
app.use('/api/profile', profile)

Next, we first write the code for the data model file. The specific code is as follows:

const mongoose = require('mongoose')

// 定义模式,用于指定数据的结构和字段。
const Schema = mongoose.Schema
// 使用Schema变量来定义具体的数据模型
const ProfileSchema = new Schema({
  type: {
    type: String,
  },
  describe: {
    type: String,
  },
  income: {
    type: String,
    required: true
  },
  expend: {
    type: String,
    required: true
  },
  cash: {
    type: String,
    required: true
  },
  remark: {
    type: String,
  },
  date: {
    type: Date,
    default: Date.now
  },
})
/** 
 * 创建了一个名为users的MongoDB集合,并使用userSchema指定了集合中文档的结构
 * 将前一步创建的模型赋值给一个变量User,使其成为我们操作users集合的接口。
 */
module.exports = Profile = mongoose.model('profile', ProfileSchema)

Based on the experience of writing interfaces explained above, we can easily write the methods for adding and obtaining information, as follows:

// 用户登录 / 注册相关的内容
const express = require('express')
const router = express.Router()
const passport = require("passport")

// 引入具体的数据类型
const Profile = require('../../models/Profile')

/**
 * 创建信息接口
 * POST api/profiles/add
 */
router.post("/add", (req, res, next) => {
  passport.authenticate("jwt", { session: false }, (err, user, info) => {
    // 判断错误情况
    if (err) return res.status(500).json({ error: "Internal Server Error" });
    if (!user) return res.status(401).json({ error: "Unauthorized" });
    const profileFields = {};
    if (req.body.type) profileFields.type = req.body.type;
    if (req.body.describe) profileFields.describe = req.body.describe;
    if (req.body.income) profileFields.income = req.body.income;
    if (req.body.expend) profileFields.expend = req.body.expend;
    if (req.body.cash) profileFields.cash = req.body.cash;
    if (req.body.remark) profileFields.remark = req.body.remark;
    new Profile(profileFields).save().then((profile) => {
        res.json(profile);
      }).catch(err => {
        res.status(500).json(err);
      })
  })(req, res, next);
})

/**
 * 获取所有信息
 * POST api/profiles
 */
router.get( '/', passport.authenticate('jwt', { session: false }), (req, res) => {
  Profile.find().then((profiles) => {
      if (!profiles || profiles.length === 0) return res.status(404).json({ error: '数据为空!' })
      const profileData = { profiles: profiles }
      res.json(profileData)
    }).catch((err) => res.status(404).json(err));
  }
);
module.exports = router

The interface for obtaining a single piece of information is also very simple. You only need to splice the id into the path, and then the front-end passes the id to us:

/**
 * 获取单个信息
 * POST api/profiles/:id
 */
router.get('/:id', passport.authenticate('jwt', { session: false }), (req, res) => {
  Profile.findOne({ _id: req.params.id }).then((profile) => {
    if (!profile) return res.status(404).json({ error: '数据为空!' });
    res.json(profile);
  }).catch((err) => res.status(404).json(err));
});

The results of the Apifox test are as follows:

Edit and delete information

The editing information interface and the adding information interface are written in roughly the same way, as follows:

/**
 * 编辑信息接口
 * POST api/profiles/edit
 */
router.post(
  "/edit/:id", 
  passport.authenticate("jwt", { session: false }), 
  (req, res) => {
    const profileFields = {};
    if (req.body.type) profileFields.type = req.body.type;
    if (req.body.describe) profileFields.describe = req.body.describe;
    if (req.body.income) profileFields.income = req.body.income;
    if (req.body.expend) profileFields.expend = req.body.expend;
    if (req.body.cash) profileFields.cash = req.body.cash;
    if (req.body.remark) profileFields.remark = req.body.remark;
    Profile.findOneAndUpdate(
      { _id: req.params.id },
      { $set: profileFields },
      { new: true }
    ).then(profile => res.json(profile))
  }
)

The interface for deleting information is also very simple:

/**
 * 删除信息
 * POST api/profiles/:id
 */
router.delete(
  '/delete/:id', 
  passport.authenticate('jwt', { session: false }), 
  (req, res) => {
    Profile.findOneAndDelete({ _id: req.params.id }).then(profile => {
      res.json(profile);
    }).catch(err => res.status(404).json('删除失败'));    
});

Indicates successful deletion!

Upload warehouse

Create a remote warehouse: Enter the github website, log in to your account and click the avatar in the upper right corner to create a new warehouse, as follows

After the creation is completed, you will jump to the following interface. The above code tells you in detail how to submit the code to the remote library:  

To submit the code, you need to borrow a git tool. How to download it will not be described here. You can use Baidu yourself. If you want to learn more about the use of git, you can refer to my previous column git column. The detailed operations are as follows:

Generate local warehouse: The specific steps are as follows:

git init generates workspace

git status View submitted files

git add . Submit to the staging area

git commit -m "Submit information" Submit to version area

Push to remote warehouse: The specific steps are as follows:

git remote add origin [email protected]:ztK63LrD/ associated alias

git push -u origin main push branch

After the push is completed, refreshing the github page will display the code we pushed on the page, as follows:

The general content of the project has been completed so far. The github source code address of the project is:Project address. If you think it is good, you can give a one-click triple link or a star for the project source address. Your support is the biggest motivation for bloggers to create. For the back-end source code, you can refer to the article: Front-end article , which will share the back-end source code.

Guess you like

Origin blog.csdn.net/qq_53123067/article/details/133955425