项目实战:构建一个Web应用(上篇)

3,732 阅读2分钟

在这个项目实战中,我们将构建一个待办事项管理Web应用。用户可以注册、登录、添加待办事项、修改待办事项状态、删除待办事项等。本文将详细介绍项目的基本架构和后端技术。

## 1. 项目简介

### 1.1 技术栈

以下是本项目实战所使用的技术栈:

  • 后端:Node.js、Express
  • 数据库:MongoDB
  • 前端(下篇文章中):HTML、CSS、JavaScript、Bootstrap、jQuery
  • 版本控制:Git

## 2. 项目准备

### 2.1 安装相关工具

确保你已经安装了以下工具:

  1. Node.js
  2. npm(Node.js包管理工具,通常与Node.js一起安装)
  3. Git
  4. MongoDB

### 2.2 创建项目文件夹

打开命令行工具,创建项目文件夹并进入:

mkdir todo-app
cd todo-app

### 2.3 初始化Git仓库

在项目文件夹中初始化Git仓库:

git init

创建.gitignore文件,排除一些不需要跟踪的文件和文件夹:

node_modules/
*.log

## 3. 后端搭建

### 3.1 初始化项目

运行npm init,按照提示填写信息,初始化项目。这将生成一个package.json文件,用于管理项目依赖和配置。

### 3.2 安装依赖

安装后端所需的依赖:

npm install express mongoose bcryptjs jsonwebtoken

  • express:Web应用框架
  • mongoose:MongoDB对象模型工具
  • bcryptjs:加密库
  • jsonwebtoken:生成和验证JSON Web Token(JWT)

### 3.3 搭建服务器

创建一个名为server.js的文件,并写入以下代码:

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

// 使用JSON中间件来解析请求体
app.use(express.json());

app.get("/", (req, res) => {
  res.send("Hello World!");
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

运行node server.js启动服务器,访问http://localhost:3000,你应该看到"Hello World!"。

### 3.4 数据库连接

确保你已经安装并启动了MongoDB。在server.js中添加以下代码以连接数据库:

const mongoose = require("mongoose");

mongoose.connect("mongodb://localhost:27017/todo_app", {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
  .then(() => console.log("Connected to MongoDB"))
  .catch((err) => console.error("Could not connect to MongoDB", err));

现在,我们已经成功连接到了MongoDB数据库。接下来,我们将创建用户认证和待办事项相关的接口。

### 3.5 用户认证

首先,我们要创建用户模型。在项目根目录下创建一个名为models的文件夹。然后,在models文件夹中创建一个名为user.js的文件,并添加以下代码:

const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
});

// 在保存用户之前,对密码进行哈希处理
userSchema.pre("save", async function (next) {
  if (!this.isModified("password")) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

// 验证密码的实例方法
userSchema.methods.validatePassword = function (password) {
  return bcrypt.compare(password, this.password);
};

const User = mongoose.model("User", userSchema);

module.exports = User;

接下来,我们将创建注册和登录接口。在项目根目录下创建一个名为routes的文件夹。然后,在routes文件夹中创建一个名为auth.js的文件,并添加以下代码:

const express = require("express");
const router = express.Router();
const jwt = require("jsonwebtoken");
const User = require("../models/user");

router.post("/register", async (req, res) => {
  try {
    const user = new User(req.body);
    await user.save();
    res.status(201).send({ message: "User registered successfully" });
  } catch (err) {
    res.status(400).send({ error: err.message });
  }
});

router.post("/login", async (req, res) => {
  try {
    const user = await User.findOne({ username: req.body.username });
    if (!user || !(await user.validatePassword(req.body.password))) {
      throw new Error("Invalid username or password");
    }
    const token = jwt.sign({ userId: user._id }, "your_jwt_secret");
    res.send({ token });
  } catch (err) {
    res.status(401).send({ error: err.message });
  }
});

module.exports = router;

最后,在server.js中添加以下代码来挂载认证路由:

const authRoutes = require("./routes/auth");

// ...

app.use("/auth", authRoutes);

// ...

现在,我们已经完成了用户认证相关的接口。可以使用Postman等API测试工具测试注册和登录接口。

### 3.6 待办事项接口

首先,我们要创建待办事项模型。在models文件夹中创建一个名为todo.js的文件,并添加以下代码:

const mongoose = require("mongoose");

const todoSchema = new mongoose.Schema({
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "User",
    required: true,
  },
  title: {
    type: String,
    required: true,
  },
  completed: {
    type: Boolean,
    default: false,
  },
});

const Todo = mongoose.model("Todo", todoSchema);

module.exports = Todo;

接下来,我们将创建待办事项相关的接口。在routes文件夹中创建一个名为todos.js的文件,并添加以下代码:

const express = require("express");
const router = express.Router();
const Todo = require("../models/todo");

router.get("/", async (req, res) => {
  try {
    const todos = await Todo.find({});
    res.send(todos);
  } catch (err) {
    res.status(500).send({ error: err.message });
  }
});

router.post("/", async (req, res) => {
  try {
    const todo = new Todo(req.body);
    await todo.save();
    res.status(201).send(todo);
  } catch (err) {
    res.status(400).send({ error: err.message });
  }
});

router.put("/:id", async (req, res) => {
  try {
    const todo = await Todo.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
      runValidators: true,
    });
    if (!todo) throw new Error("Todo not found");
    res.send(todo);
  } catch (err) {
    res.status(400).send({ error: err.message });
  }
});

router.delete("/:id", async (req, res) => {
  try {
    const todo = await Todo.findByIdAndDelete(req.params.id);
    if (!todo) throw new Error("Todo not found");
    res.send({ message: "Todo deleted successfully" });
  } catch (err) {
    res.status(400).send({ error: err.message });
  }
});

module.exports = router;

最后,在server.js中添加以下代码来挂载待办事项路由:

const todoRoutes = require("./routes/todos");

// ...

app.use("/todos", todoRoutes);

// ...

至此,我们已经完成了待办事项接口的开发。可以使用Postman等API测试工具测试待办事项相关接口。

## 4. 启动Web应用

在完成上篇文章中的步骤后,我们已经搭建好了后端服务器。现在我们需要启动后端服务器:

  1. 确保已经安装并运行了MongoDB。
  2. 打开命令行(终端),进入项目文件夹todo-app
cd todo-app
```

  1. 运行server.js文件以启动服务器: 
node server.js
```

如果一切正常,你应该看到以下输出:

````sh
Server running on port 3000
Connected to MongoDB
```

现在,后端服务器已经在端口3000上运行,并与MongoDB数据库连接。接下来,我们将构建前端部分。

## 5. 前端搭建

### 5.1 创建前端文件夹和文件

在项目根目录下创建一个名为public的文件夹,用于存放前端文件。接下来,在public文件夹中创建以下文件:

  • index.html:主页面
  • style.css:样式表
  • script.js:JavaScript脚本

### 5.2 编写前端页面

编辑index.html文件,添加以下代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1 class="text-center mt-5">Todo App</h1>
        <!-- 在这里添加表单和待办事项列表 -->
    </div>

    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
    <script src="script.js"></script>
</body>
</html>

5.3 添加表单和待办事项列表

index.html文件的<!-- 在这里添加表单和待办事项列表 -->位置添加以下代码:

<form class="mt-4" id="todo-form">
    <div class="form-group">
        <label for="title">Title</label>
        <input type="text" class="form-control" id="title" placeholder="Enter todo title">
    </div>
    <button type="submit" class="btn btn-primary">Add Todo</button>
</form>

<div class="mt-4">
    <h3 class="mb-3">Todo List</h3>
    <ul class="list-group" id="todo-list">
        <!-- 在这里显示待办事项 -->
    </ul>
</div>

5.4 编写CSS样式

编辑style.css文件,添加以下代码:

body {
    font-family: Arial, sans-serif;
}

.completed {
    text-decoration: line-through;
}

5.5 编写JavaScript脚本

编辑script.js文件,添加以下代码:

$(document).ready(function () {
  // 获取待办事项列表
  function getTodos() {
    // 在这里编写获取待办事项列表的代码
  }

  // 添加待办事项
  function addTodo() {
    // 在这里编写添加待办事项的代码
  }

  // 修改待办事项状态
  function toggleTodo() {
    // 在这里编写修改待办事项状态的代码
  }

  // 删除待办事项
  function deleteTodo() {
    // 在这里编写删除待办事项的代码
  }

  // 事件绑定
  $("#todo-form").on("submit", function (event) {
    event.preventDefault();
    addTodo();
  });

  $("#todo-list").on("click", ".toggle-todo", function () {
    toggleTodo();
  });

  $("#todo-list").on("click", ".delete-todo", function () {
    deleteTodo();
  });

  // 初始化
  getTodos();
});
``在`script.js`中,我们已经创建了基本的函数框架。接下来,我们需要实现这些函数,以便与后端服务器进行交互。

### 5.6 实现获取待办事项列表函数

在`getTodos`函数中添加以下代码:

```javascript
$.get("/api/todos", function (data) {
  $("#todo-list").empty();
  data.forEach(function (todo) {
    var listItem = $("<li class='list-group-item d-flex justify-content-between align-items-center'></li>");
    var title = $("<span class='todo-title'></span>").text(todo.title);
    if (todo.completed) {
      title.addClass("completed");
    }
    var buttonGroup = $("<div class='btn-group'></div>");
    var toggleButton = $("<button class='btn btn-sm btn-outline-secondary toggle-todo'></button>").text(todo.completed ? "Undo" : "Complete");
    var deleteButton = $("<button class='btn btn-sm btn-outline-danger delete-todo'></button>").text("Delete");
    
    buttonGroup.append(toggleButton, deleteButton);
    listItem.append(title, buttonGroup);
    listItem.data("id", todo._id);
    listItem.data("completed", todo.completed);
    $("#todo-list").append(listItem);
  });
});

5.7 实现添加待办事项函数

addTodo函数中添加以下代码:

var title = $("#title").val().trim();
if (title) {
  $.post("/api/todos", { title: title }, function () {
    $("#title").val("");
    getTodos();
  });
}

5.8 实现修改待办事项状态函数

toggleTodo函数中添加以下代码:

var listItem = $(this).closest("li");
var id = listItem.data("id");
var completed = listItem.data("completed");

$.ajax({
  url: "/api/todos/" + id,
  type: "PUT",
  data: { completed: !completed },
  success: function () {
    getTodos();
  }
});

5.9 实现删除待办事项函数

deleteTodo函数中添加以下代码:

var listItem = $(this).closest("li");
var id = listItem.data("id");

$.ajax({
  url: "/api/todos/" + id,
  type: "DELETE",
  success: function () {
    getTodos();
  }
});

至此,我们已经完成了前端页面的构建。现在,你可以运行后端服务器,然后在浏览器中访问http://localhost:3000来查看并使用这个待办事项应用。

为了使应用在 Express 服务器中正确运行,请确保在 server.js 文件中添加以下代码:

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

确保将其添加到其他中间件之前,例如:

const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");

const app = express();
const PORT = process.env.PORT || 3000;

mongoose.connect("mongodb://localhost/todo_app", { useNewUrlParser: true, useUnifiedTopology: true });
mongoose.set("useFindAndModify", false);

app.use(cors());
app.use(express.json());
app.use(express.static("public")); // 将此行添加到此处
app.use("/api/todos", require("./routes/todos"));

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

其他内容我们下篇再继续 可以订阅本专栏 有更新 第一时间推送给你。