0基础实现node.js+Express+mysql 实现前后端分离的图书管理系统 全栈开发流程
我们在前端通过axios发起http请求给后端,后端返回数据,然后通过挂载到vue组件上,通过后端返回的json数据,对elementui组件进行渲染渲染完毕后,绘制页面.后端基础配置完毕.请注意避免跨域链接造成的浏览器无法访问数据的问题,我加载了cors这个模块,并且设置允许所有的端口访问.在app.js文件中已经有过配置,这里作为说明。可以看到浏览器通过json格式返回了我们的书籍书籍,后端的
一. 项目流程设计
1. 数据库设计
- 设计了
books
表,包含字段:id
,name
,author
,price
,publisher
。
2. Node.js + Express后端设计
- 初始化Express应用。
- 创建了基础路由和服务器监听。
- 配置了数据库连接。
- 实现了图书模型(
BookModel
),提供了对图书增删改查的功能。
3. 前端页面设计(Element UI)
- 使用Vue.js和Element UI创建了图书管理界面。
- 设计了图书列表展示、添加、编辑和删除的交互界面。
二. 技术栈
后端(Node.js + Express)
- 实现了RESTful API,包括获取所有图书、根据作者查询图书、删除指定ID的图书、更新指定ID的图书信息、增加新图书。
前端(Vue.js + Element UI)
- 使用Element UI组件创建了图书列表和表单。
- 实现了与后端API的交互,包括获取图书列表、添加新图书、编辑和删除图书。
测试
- 提供了测试脚本,用于验证图书模型的各种功能。
部署
- 后端部署在
http://localhost:3000
,前端页面通过Vue.js渲染并与后端API交互
三.具体操作流程
-
数据库设计
mysql建立图书数据库 建立books表,包含信息:
主键id、name,author,price,publisher
并模拟一些数据,插入到数据库中用于以后的测试 -
node.js访问数据库
a.搭建express框架
先安装node.js环境和express环境
node安装教程:Node.js下载安装及环境配置教程【超详细】_nodejs下载-CSDN博客
配置Express :Node.js Express 框架 | 菜鸟教程 (runoob.com)
配置完成
编码调试
这个的代码只是测试环境是否配置完毕,不是实际的工程。运行文件后去浏览器上访问
http://127.0.0.1:8081网址看看是否配置完成//express_demo.js 文件 var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World'); }) var server = app.listen(8081, function () { var host = server.address().address var port = server.address().port console.log("应用实例,访问地址为 http://%s:%s", host, port) })
正常运行界面
显示这个界面就算是配置完成了
b.node.js链接数据库
开始链接数据库
首先创建一个目录/config/dbConfig,js 用于保存你的数据库配置,为了方便选择了root作为用户,免去了用户创建和权限管理的步骤,实际工程开发需要保证安全创建用户并且配置合理的数据库权限
链接数据库的文件
为了保证数据库密码安全,将数据库链接配置文件和数据库配置/config/dbConfig,js 分开存放
文件database.jsconst dbConfig = require('../config/dbConfig'); // 引入mysql模块 const mysql = require('mysql'); // 创建MySQL连接 const connection = mysql.createConnection({ host: dbConfig.host, user: dbConfig.user, password: dbConfig.password, database: dbConfig.database }); // 连接到数据库 connection.connect((err) => {n if (err) { console.error('Error connecting to database: ' + err.stack); return; } console.log('Connected to database as id ' + connection.threadId); }); module.exports = connection;
数据库配置文件/config/dbConfig,js
d.链接数据库实现对图书表的增删查改
链接数据库:运行database.js文件,显示如下,则成功建立链接
建立链接后,将建立的链接导出以便给接口路由使用
module.exports = connection;
在上面的数据库链接中已经配置,
下面就是配置book模型,将book的执行功能都配置在模型中这样项目会更加正规易于维护
这里仅仅演示了部分简单功能,如果有需要可以根据你的项目自行配置功能1:显示所有书籍数据,不需要输入
功能2:输入:指定作者的书,可以进行查询
功能3:输入:指定id的书,可以进行删除
功能4:输入:指定id的书,可以进行信息修改
功能5:输入:图书信息,可以进行图书的增加
bookModel.js文件const db = require('database'); // 确保此路径正确指向您的数据库连接文件 class BookModel { // 获取所有图书 static async getAllBooks() { return new Promise((resolve, reject) => { db.query('SELECT * FROM books', (err, results) => { if (err) return reject(err); resolve(results); }); }); } // 根据作者查询图书 static async getBooksByAuthor(author) { return new Promise((resolve, reject) => { db.query('SELECT * FROM books WHERE author = ?', [author], (err, results) => { if (err) return reject(err); resolve(results); }); }); } // 根据ID删除图书 static async deleteBookById(bookId) { return new Promise((resolve, reject) => { db.query('DELETE FROM books WHERE id = ?', [bookId], (err, results) => { if (err) return reject(err); resolve(results); }); }); } // 根据ID更新图书信息 static async updateBookById(bookId, bookData) { return new Promise((resolve, reject) => { const { name, author, price, publisher } = bookData; db.query( 'UPDATE books SET name = ?, author = ?, price = ?, publisher = ? WHERE id = ?', [name, author, price, publisher, bookId], (err, results) => { if (err) return reject(err); resolve(results); } ); }); } // 增加新图书 static async addBook(newBook) { return new Promise((resolve, reject) => { const { name, author, price, publisher } = newBook; db.query( 'INSERT INTO books (name, author, price, publisher) VALUES (?, ?, ?, ?)', [name, author, price, publisher], (err, results) => { if (err) return reject(err); resolve(results); } ); }); } } module.exports = BookModel;
配置完模型文件后要导出你的bookMode以便配置接口框架,module.exports = BookModel;
编写测试测试bookModel模型
const BookModel = require('bookModel'); // 调整路径以匹配您的文件结构 // 测试获取所有图书 async function testGetAllBooks() { try { const books = await BookModel.getAllBooks(); console.log('测试获取所有图书:'); console.log(books); } catch (error) { console.error('测试获取所有图书失败:', error); } } // 测试根据作者查询图书 async function testGetBooksByAuthor() { try { const author = '某个作者'; // 替换为实际的作者名称 const booksByAuthor = await BookModel.getBooksByAuthor(author); console.log(`测试根据作者 '${author}' 查询图书:`); console.log(booksByAuthor); } catch (error) { console.error(`测试根据作者 '${author}' 查询图书失败:`, error); } } // 测试删除图书 async function testDeleteBookById() { try { const bookId = 1; // 替换为实际的图书ID await BookModel.deleteBookById(bookId); console.log(`测试删除图书ID ${bookId}:`); console.log('删除成功'); } catch (error) { console.error(`测试删除图书ID ${bookId} 失败:`, error); } } // 测试更新图书信息 async function testUpdateBookById() { try { const bookId = 1; // 替换为实际的图书ID const bookData = { name: '新书名', author: '新作者', price: 99.99, publisher: '新出版社' }; await BookModel.updateBookById(bookId, bookData); console.log(`测试更新图书ID ${bookId} 信息:`); console.log('更新成功'); } catch (error) { console.error(`测试更新图书ID ${bookId} 信息失败:`, error); } } // 测试增加新图书 async function testAddBook() { try { const newBook = { name: '新书名', author: '新作者', price: 99.99, publisher: '新出版社' }; await BookModel.addBook(newBook); console.log('测试增加新图书:'); console.log('增加成功'); } catch (error) { console.error('测试增加新图书失败:', error); } } // 执行所有测试函数 testGetAllBooks(); testGetBooksByAuthor(); testDeleteBookById(); // 注意:这将删除实际的数据库记录,请谨慎使用 testUpdateBookById(); // 注意:确保提供的ID存在 testAddBook();
首先运行database文件建立数据库链接,然后运行测试模型文件
模型功能测试完毕<进入下一个环节
e.为前端各个功能实现api接口
下面就是通过express框架实现路由配置
routes.jsconst express = require('express'); const router = express.Router(); const BookModel = require('bookModel'); // 确保此路径正确指向您的模型文件GET http://localhost:3000/api/books // 功能1:显示所有书籍数据 router.get('/books', async (req, res) => { try { const books = await BookModel.getAllBooks(); res.json(books); } catch (err) { res.status(500).json({ message: 'Error retrieving books', error: err.message }); } }); // 功能2:输入指定作者,查询书籍 router.get('/books', async (req, res) => { try { const author = req.query.author; // 从查询参数中获取作者 const booksByAuthor = await BookModel.getBooksByAuthor(author); res.json(booksByAuthor); } catch (err) { res.status(500).json({ message: 'Error retrieving books by author', error: err.message }); } }); // 功能3:输入指定id,删除书籍 router.delete('/books/:id', async (req, res) => { try { const bookId = req.params.id; await BookModel.deleteBookById(bookId); res.status(204).end(); } catch (err) { res.status(500).json({ message: 'Error deleting book', error: err.message }); } }); // 功能4:输入指定id,修改书籍信息 router.put('/books/:id', async (req, res) => { try { const bookId = req.params.id; const updateData = req.body; // 获取请求体中的书籍信息 await BookModel.updateBookById(bookId, updateData); res.status(200).json({ message: 'Book updated successfully' }); } catch (err) { res.status(500).json({ message: 'Error updating book', error: err.message }); } }); // 功能5:输入图书信息,增加图书 router.post('/books', async (req, res) => { try { const newBook = req.body; // 获取请求体中的书籍信息 await BookModel.addBook(newBook); res.status(201).json({ message: 'Book added successfully' }); } catch (err) { res.status(500).json({ message: 'Error adding book', error: err.message }); } }); module.exports = router; /*mysql> CREATE TABLE books ( -> id INT AUTO_INCREMENT PRIMARY KEY, -> name VARCHAR(255) NOT NULL, -> author VARCHAR(255) NOT NULL, -> price DECIMAL(10, 2) NOT NULL, -> publisher VARCHAR(255) NOT NULL -> );*/
f.使用express服务搭建并启用app服务
app.jsconst express = require('express'); const app = express(); const port = 3000; const cors = require('cors'); // 引入上面编写的路由 const bookRouter = require('routes'); // 确保此路径正确指向您的路由文件 app.use(cors()); // 使用中间件来解析请求体 app.use(express.json()); // 挂载路由 app.use('/api', bookRouter); // 将路由挂载到'/api'路径下 // 启动服务器 app.listen(port, () => { console.log(`Server running on port ${port}`); });
测试后端服务是否启动成功
运行app.js后打开浏览器地址栏输入http://localhost:3000/api/books
因为浏览器默认可以发送git命令,git http://localhost:3000/api/books被我们配置为获取所有书籍信息,
可以看到浏览器通过json格式返回了我们的书籍书籍,后端的其他命令通过浏览器测试无法达到,可以通过postman进行api测试
后端基础配置完毕.请注意避免跨域链接造成的浏览器无法访问数据的问题,我加载了cors这个模块,并且设置允许所有的端口访问.在app.js文件中已经有过配置,这里作为说明 -
开始配置前端文件
前端的思路是这样的:
我们在前端通过axios发起http请求给后端,后端返回数据,然后通过挂载到vue组件上,通过后端返回的json数据,对elementui组件进行渲染渲染完毕后,绘制页面.
首先导入vue和elementui
然后书写vue基础模块挂载模型,然后将elementui作为模型渲染,这里代码讲解的话涉及的就太多了,直接给出代码,为了方便配置代码文件设置为一个book.html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Bookstore App</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui/lib/theme-chalk/index.css"> <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/element-ui/lib/index.js"></script> </head> <body> <div id="app"> <el-card> <el-button type="primary" @click="showAddBookForm">添加书籍</el-button> <el-table :data="books" style="width: 100%; margin-top: 20px;"> <el-table-column prop="id" label="ID" width="50"></el-table-column> <el-table-column prop="name" label="书名"></el-table-column> <el-table-column prop="author" label="作者"></el-table-column> <el-table-column prop="price" label="价格"></el-table-column> <el-table-column prop="publisher" label="出版社"></el-table-column> <el-table-column label="操作" width="150"> <template slot-scope="scope"> <el-button @click="showEditBookForm(scope.row)">编辑</el-button> <el-button @click="deleteBook(scope.row.id)" type="danger">删除</el-button> </template> </el-table-column> </el-table> </el-card> <!-- 添加书籍表单 --> <el-dialog :visible.sync="addBookFormVisible" title="添加书籍" @close="resetAddBookForm"> <el-form :model="newBook" :rules="bookFormRules" ref="addBookForm"> <el-form-item label="书名" prop="name" label-width="100px"> <el-input v-model="newBook.name"></el-input> </el-form-item> <el-form-item label="作者" prop="author" label-width="100px"> <el-input v-model="newBook.author"></el-input> </el-form-item> <el-form-item label="价格" prop="price" label-width="100px"> <el-input v-model="newBook.price"></el-input> </el-form-item> <el-form-item label="出版社" prop="publisher" label-width="100px"> <el-input v-model="newBook.publisher"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="addBookFormVisible = false">取消</el-button> <el-button type="primary" @click="addBook">确定</el-button> </span> </el-dialog> <!-- 编辑书籍表单 --> <el-dialog :visible.sync="editBookFormVisible" title="编辑书籍" @close="resetEditBookForm"> <el-form :model="currentBook" :rules="bookFormRules" ref="editBookForm"> <!-- 使用v-bind绑定currentBook的数据到表单项 --> <el-form-item label="书名" prop="name" label-width="100px"> <el-input v-model="currentBook.name"></el-input> </el-form-item> <!-- ...其他表单项与添加书籍相同... --> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="editBookFormVisible = false">取消</el-button> <el-button type="primary" @click="updateBook">更新</el-button> </span> </el-dialog> </div> <script> const app = new Vue({ el: '#app', data() { return { books: [], newBook: { id: null, name: '', author: '', price: '', publisher: '' }, currentBook: {}, addBookFormVisible: false, editBookFormVisible: false, bookFormRules: { name: [ { required: true, message: '请输入书名', trigger: 'blur' }, { min: 3, max: 50, message: '长度为 3 到 50 个字符', trigger: 'blur' } ], author: [ { required: true, message: '请输入作者名', trigger: 'blur' } ], price: [ { required: true, message: '请输入价格', trigger: 'blur' } ], publisher: [ { required: true, message: '请输入出版社', trigger: 'blur' } ] } }; }, methods: { async fetchBooks() { try { const response = await axios.get('http://localhost:3000/api/books'); this.books = response.data; } catch (error) { console.error('Error fetching books:', error); this.$message.error('无法获取书籍列表'); } }, showAddBookForm() { this.newBook = { id: null, name: '', author: '', price: '', publisher: '' }; this.addBookFormVisible = true; }, resetAddBookForm() { this.$refs.addBookForm.resetFields(); }, async addBook() { this.$refs.addBookForm.validate(async (valid) => { if (valid) { try { const response = await axios.post('http://localhost:3000/api/books', this.newBook); this.addBookFormVisible = false; this.fetchBooks(); // 刷新书籍列表 this.$message.success('书籍添加成功'); } catch (error) { console.error('Error adding book:', error); this.$message.error('添加书籍失败'); } } }); }, showEditBookForm(book) { this.currentBook = Object.assign({}, book); this.editBookFormVisible = true; }, resetEditBookForm() { this.$refs.editBookForm.resetFields(); }, async updateBook() { this.$refs.editBookForm.validate(async (valid) => { if (valid) { try { const response = await axios.put(`http://localhost:3000/api/books/${this.currentBook.id}`, this.currentBook); this.editBookFormVisible = false; await this.fetchBooks(); // 刷新书籍列表 this.$message.success('书籍更新成功'); } catch (error) { console.error('Error updating book:', error); this.$message.error('更新书籍失败'); } } }); }, async deleteBook(bookId) { try { await axios.delete(`http://localhost:3000/api/books/${bookId}`); await this.fetchBooks(); // 刷新书籍列表 this.$message.success('书籍删除成功'); } catch (error) { console.error('Error deleting book:', error); this.$message.error('删除书籍失败'); } } }, mounted() { this.fetchBooks(); } }); </script> </body> </html>
至此所有前后端所有项目配置完毕
我们来看看实际前端的效果
图书列表首页
然后点击添加书籍
添加成功
编辑
删除,点击对应博客的删除可以完成删除
更多推荐
所有评论(0)