0基础实现node.js+Express+mysql 实现前后端分离的图书管理系统 全栈开发流程

一. 项目流程设计

1. 数据库设计
  • 设计了books表,包含字段:idnameauthorpricepublisher
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交互

三.具体操作流程

  1. 数据库设计

    mysql建立图书数据库 建立books表,包含信息:

    主键id、name,author,price,publisher


    并模拟一些数据,插入到数据库中用于以后的测试

  2. 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.js

    const 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.js

    const 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.js 

    const 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文件中已经有过配置,这里作为说明

  3. 开始配置前端文件
    前端的思路是这样的:
         我们在前端通过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>

    至此所有前后端所有项目配置完毕
    我们来看看实际前端的效果
    图书列表首页

    然后点击添加书籍

    添加成功

    编辑

    删除,点击对应博客的删除可以完成删除

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值