B站学习node.js,用Expres实现简单的增删改查

tech2026-02-15  0

Express - crud

开发工具

vsCode

起步

初始化模板处理

路由设计

请求方法请求路径get 参数post 参数备注GET/students渲染首页GET/students/new添加学生页面POST/students/newname、age、gender、hobbies处理添加学生请求GET/students/editid渲染编辑页面POST/students/editid、name、age、gender、hobbies处理编辑学生请求GET/students/deleteid处理删除学生请求

备注配置 art-template 模板引擎

安装 npm install -S art-template express-art-template

在 Express 获取表单 POST 请求体数据

npm install -S body-parser

文件信息

db.json文件 本设计中没有运用数据库开发,所以在此用文件读取的方式来代替数据库,后续再更新增加了数据库的版本。文件内容 { "students": [ { "id": 1, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,敲代码" }, { "id": 2, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,敲代码" }, { "id": 3, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,敲代码" }, { "id": 5, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,敲代码" }, { "id": 6, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,敲代码" }, { "name": "李四", "gender": "0", "age": "22", "hobbies": "敲代码,跳绳", "id": 8 } ] }

设计操作数据的文件模块

/** * student.js * 数据操作文件模块 * 职责:操作文件中的数据,只处理数据,不关心业务 * * 这里才是我们学习 Node 的精华部分:奥义之所在 * 封装异步 API */ var fs = require('fs') var dbPath = './db.json' /** * 获取学生列表 * @param {Function} callback 回调函数 */ exports.find = function (callback) { fs.readFile(dbPath, 'utf8', function (err, data) { if (err) { return callback(err) } callback(null, JSON.parse(data).students) }) } /** * 根据 id 获取学生信息对象 * @param {Number} id 学生 id * @param {Function} callback 回调函数 */ exports.findById = function (id, callback) { fs.readFile(dbPath, 'utf8', function (err, data) { if (err) { return callback(err) } var students = JSON.parse(data).students var ret = students.find(function (item) { return item.id === parseInt(id) }) callback(null, ret) }) } /** * 添加保存学生 * @param {Object} student 学生对象 * @param {Function} callback 回调函数 */ exports.save = function (student, callback) { fs.readFile(dbPath, 'utf8', function (err, data) { if (err) { return callback(err) } var students = JSON.parse(data).students // 添加 id ,唯一不重复 student.id = students[students.length - 1].id + 1 // 把用户传递的对象保存到数组中 students.push(student) // 把对象数据转换为字符串 var fileData = JSON.stringify({ students: students }) // 把字符串保存到文件中 fs.writeFile(dbPath, fileData, function (err) { if (err) { // 错误就是把错误对象传递给它 return callback(err) } // 成功就没错,所以错误对象是 null callback(null) }) }) } /** * 更新学生 */ exports.updateById = function (student, callback) { fs.readFile(dbPath, 'utf8', function (err, data) { if (err) { return callback(err) } var students = JSON.parse(data).students // 注意:这里记得把 id 统一转换为数字类型 student.id = parseInt(student.id) // 你要修改谁,就需要把谁找出来 // EcmaScript 6 中的一个数组方法:find // 需要接收一个函数作为参数 // 当某个遍历项符合 item.id === student.id 条件的时候,find 会终止遍历,同时返回遍历项 var stu = students.find(function (item) { return item.id === student.id }) // 这种方式你就写死了,有 100 个难道就写 100 次吗? // stu.name = student.name // stu.age = student.age // 遍历拷贝对象 for (var key in student) { stu[key] = student[key] } // 把对象数据转换为字符串 var fileData = JSON.stringify({ students: students }) // 把字符串保存到文件中 fs.writeFile(dbPath, fileData, function (err) { if (err) { // 错误就是把错误对象传递给它 return callback(err) } // 成功就没错,所以错误对象是 null callback(null) }) }) } /** * 删除学生 */ exports.deleteById = function (id, callback) { fs.readFile(dbPath, 'utf8', function (err, data) { if (err) { return callback(err) } var students = JSON.parse(data).students // findIndex 方法专门用来根据条件查找元素的下标 var deleteId = students.findIndex(function (item) { return item.id === parseInt(id) }) // 根据下标从数组中删除对应的学生对象 students.splice(deleteId, 1) // 把对象数据转换为字符串 var fileData = JSON.stringify({ students: students }) // 把字符串保存到文件中 fs.writeFile(dbPath, fileData, function (err) { if (err) { // 错误就是把错误对象传递给它 return callback(err) } // 成功就没错,所以错误对象是 null callback(null) }) }) }

app.js入门模块

/** * app.js入门模块 * 职责: * 创建服务 * 做一些服务相关配置 * 模板引擎 * body-parse 解析post请求体 * 提供静态服务资源 * 挂载路由 * 监听端口启动服务 */ var express = require("express"); var router = require("./router"); const bodyParser = require("body-parser"); var app = express(); app.use("/public/", express.static("./public/")); //相当于读取静态文件 app.engine("html", require("express-art-template")); //配置模板引擎和body-parser一定要在app.use(router)挂载路由之前 app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // router(app); //把路由容器挂载到app服务中 app.use(router); app.listen(2000, function () { console.log("running 2000......"); }); module.exports = app; //导出app

router.js 路由模块

这里的路由模块设计的思想是根据回调函数的思想来设计的。

/** * router.js 路由模块 * 职责: * 处理路由 * 根据不同的请求方法+请求路径设置具体的请求处理函数 * 模块职责要单一 */ //把app加载进来 // var app = require("./app"); var fs = require("fs"); var Student = require("./student"); //express提供了一个很好的方式 //专门用来包装路由的 var express = require("express"); //1、创建一个路由容器 var router = express.Router(); //2、把路由挂载到router路由容器中 router.get("/students", function (req, res) { //readFile的第二个参数是可选的,传入utf8 就是告诉它把读取到的文件直接按照utf8编码转成我们能认识的字符 //也可以通过data.toString()的方式 // fs.readFile("./db.json", "utf8", function (err, data) { // if (err) { // return res.status(500).send("Server Error!"); // } // res.render("index.html", { // fruits: ["苹果", "香蕉", "葡萄", "橘子"], // students: JSON.parse(data).students, // }); // }); Student.find(function (err, students) { if (err) { return res.status(500).send("Server Error!"); } res.render("index.html", { fruits: ["苹果", "香蕉", "葡萄", "橘子"], students: students, }); }); }); router.get("/students/new", function (req, res) { res.render("new.html"); }); router.post("/students/new", function (req, res) { //1、获取表单请求 //2、处理 //将数据保存到db.json文件中以持久化 //3、发送响应 //先读取处理,转成对象 //然后往数据中push数据 //将对象转换为字符串 //最后将字符串写入文件 Student.save(req.body, function (err) { if (err) { return res.status(500).send("Server Error!"); } res.redirect("/students"); }); }); router.get("/students/edit", function (req, res) { //1、在客户端的列表页中处理链接问题(需要id参数) //2、获取需要编辑学生的id //3、渲染编辑页面 Student.findById(parseInt(req.query.id), function (err, student) { if (err) { return res.status(500).send("Server Error!"); } res.render("edit.html", { student: student, }); }); }); router.post("/students/edit", function (req, res) { Student.updateById(req.body, function (err) { if (err) { return res.status(500).send("Server Error!"); } res.redirect("/students"); }); }); router.get("/students/delete", function (req, res) { //1、获取要删除的id //2、根据id进行删除操作 //3、根据操作结果发送响应数据 Student.deleteById(req.query.id, function (err) { if (err) { return res.status(500).send("Server Error!"); } res.redirect("/students"); }); }); //3、把router导出 module.exports = router; //这样使用不方便 // module.exports = function (app) { // app.get("/students", function (req, res) { // //readFile的第二个参数是可选的,传入utf8 就是告诉它把读取到的文件直接按照utf8编码转成我们能认识的字符 // //也可以通过data.toString()的方式 // fs.readFile("./db.json", "utf8", function (err, data) { // if (err) { // return res.status(500).send("Server Error!"); // } // res.render("index.html", { // fruits: ["苹果", "香蕉", "葡萄", "橘子"], // students: JSON.parse(data).students, // }); // }); // }); // };

views页面展示效果

以下这些页面的实现都是在bootstrap官网上找的模板。 链接

https://v3.bootcss.com/examples/dashboard/

index.html首页

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content="" /> <meta name="author" content="" /> <!-- <link rel="icon" href="../../favicon.ico" /> --> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <link href="../../assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet" /> <!-- Custom styles for this template --> <link rel="stylesheet" href="../public/css/main.css" /> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar" > <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Project name</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> <form class="navbar-form navbar-right"> <input type="text" class="form-control" placeholder="Search..." /> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"> <a href="#">Overview <span class="sr-only">(current)</span></a> </li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item</a></li> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> <li><a href="">More navigation</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h1 class="page-header">Dashboard</h1> <div class="row placeholders"> {{ each fruits }} <div class="col-xs-6 col-sm-3 placeholder"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail" /> <h4>{{ $value }}</h4> <span class="text-muted">Something else</span> </div> {{ /each }} </div> <h2 class="sub-header">Section title</h2> <a class="btn btn-success" href="/students/new">添加学生</a> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>#</th> <th>姓名</th> <th>性别</th> <th>年龄</th> <th>爱好</th> <th>操作</th> </tr> </thead> <tbody> {{each students}} <tr> <td>{{$value.id}}</td> <td>{{$value.name}}</td> <td>{{$value.gender}}</td> <td>{{$value.age}}</td> <td>{{$value.hobbies}}</td> <td> <a href="/students/edit?id={{$value.id}}">编辑</a> <a href="/students/delete?id={{$value.id}}">删除</a> </td> </tr> {{/each}} </tbody> </table> </div> </div> </div> </div> </body> </html>

new.html添加学生页面

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content="" /> <meta name="author" content="" /> <!-- <link rel="icon" href="../../favicon.ico" /> --> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <link href="../../assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet" /> <!-- Custom styles for this template --> <link rel="stylesheet" href="../public/css/main.css" /> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar" > <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Project name</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> <form class="navbar-form navbar-right"> <input type="text" class="form-control" placeholder="Search..." /> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"> <a href="#">Overview <span class="sr-only">(current)</span></a> </li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item</a></li> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> <li><a href="">More navigation</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">添加学生</h2> <form action="/students/new" method="POST"> <div class="form-group"> <label for="name">姓名</label> <input type="text" class="form-control" id="name" name="name" placeholder="Email" /> </div> <div class="form-group"> <label>性别</label> <div> <label class="radio-inline"> <input type="radio" name="gender" id="gender1" value="0" /></label> <label class="radio-inline"> <input type="radio" name="gender" id="gender2" value="1" /></label> </div> </div> <div class="form-group"> <label for="age">年龄</label> <input type="number" id="age" name="age" class="form-control" /> </div> <div class="form-group"> <label>爱好</label> <input type="text" id="hobbies" name="hobbies" class="form-control" /> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div> </div> </body> </html>

edit.html编辑学生页面

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content="" /> <meta name="author" content="" /> <!-- <link rel="icon" href="../../favicon.ico" /> --> <title>Dashboard Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" /> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <link href="../../assets/css/ie10-viewport-bug-workaround.css" rel="stylesheet" /> <!-- Custom styles for this template --> <link rel="stylesheet" href="../public/css/main.css" /> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar" > <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">Project name</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Dashboard</a></li> <li><a href="#">Settings</a></li> <li><a href="#">Profile</a></li> <li><a href="#">Help</a></li> </ul> <form class="navbar-form navbar-right"> <input type="text" class="form-control" placeholder="Search..." /> </form> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"> <a href="#">Overview <span class="sr-only">(current)</span></a> </li> <li><a href="#">Reports</a></li> <li><a href="#">Analytics</a></li> <li><a href="#">Export</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item</a></li> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> <li><a href="">More navigation</a></li> </ul> <ul class="nav nav-sidebar"> <li><a href="">Nav item again</a></li> <li><a href="">One more nav</a></li> <li><a href="">Another nav item</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <h2 class="sub-header">编辑学生</h2> <form action="/students/edit" method="POST"> <input type="hidden" name="id" value="{{student.id}}" /> <div class="form-group"> <label for="name">姓名</label> <input type="text" class="form-control" id="name" name="name" placeholder="Email" value="{{student.name}}" /> </div> <div class="form-group"> <label>性别</label> <div> <label class="radio-inline active"> <input type="radio" name="gender" id="gender1" value="0" /></label> <label class="radio-inline"> <input type="radio" name="gender" id="gender2" value="1" /></label> </div> </div> <div class="form-group"> <label for="age">年龄</label> <input type="number" id="age" name="age" class="form-control" value="{{student.age}}" /> </div> <div class="form-group"> <label>爱好</label> <input type="text" id="hobbies" name="hobbies" class="form-control" value="{{student.hobbies}}" /> </div> <button type="submit" class="btn btn-default">Submit</button> </form> </div> </div> </div> </body> </html>

最后有一个需要的css样式

/* * Base structure */ /* Move down content because we have a fixed navbar that is 50px tall */ body { padding-top: 50px; } /* * Global add-ons */ .sub-header { padding-bottom: 10px; border-bottom: 1px solid #eee; } /* * Top navigation * Hide default border to remove 1px line. */ .navbar-fixed-top { border: 0; } /* * Sidebar */ /* Hide for mobile, show later */ .sidebar { display: none; } @media (min-width: 768px) { .sidebar { position: fixed; top: 51px; bottom: 0; left: 0; z-index: 1000; display: block; padding: 20px; overflow-x: hidden; overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ background-color: #f5f5f5; border-right: 1px solid #eee; } } /* Sidebar navigation */ .nav-sidebar { margin-right: -21px; /* 20px padding + 1px border */ margin-bottom: 20px; margin-left: -20px; } .nav-sidebar > li > a { padding-right: 20px; padding-left: 20px; } .nav-sidebar > .active > a, .nav-sidebar > .active > a:hover, .nav-sidebar > .active > a:focus { color: #fff; background-color: #428bca; } /* * Main content */ .main { padding: 20px; } @media (min-width: 768px) { .main { padding-right: 40px; padding-left: 40px; } } .main .page-header { margin-top: 0; } /* * Placeholder dashboard ideas */ .placeholders { margin-bottom: 30px; text-align: center; } .placeholders h4 { margin-bottom: 0; } .placeholder { margin-bottom: 20px; } .placeholder img { display: inline-block; border-radius: 50%; }
最新回复(0)