使用 Node.js 开发任务管理应用程序的后端

使用 Node.js 开发任务管理应用程序的后端

引言

大家好!欢迎来到今天的讲座,今天我们来聊聊如何使用 Node.js 开发一个任务管理应用程序的后端。如果你对 Node.js 有基本的了解,或者你是一个初学者,那么这篇文章将会非常适合你。我们将从零开始,一步步构建一个功能齐全的任务管理应用,涵盖从项目初始化到部署的全过程。别担心,我会尽量用轻松诙谐的语言,让你在学习的过程中不会感到枯燥。

为什么选择 Node.js?

首先,我们来聊聊为什么选择 Node.js 来开发这个任务管理应用的后端。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它允许我们在服务器端编写 JavaScript 代码。Node.js 的非阻塞 I/O 模型使得它在处理大量并发请求时表现非常出色,非常适合构建高效的 API 和实时应用。

此外,Node.js 拥有庞大的社区支持和丰富的第三方库(NPM),这意味着你可以轻松找到各种现成的工具和模块来加速开发过程。最重要的是,Node.js 让前端开发者可以无缝过渡到后端开发,因为它们都使用相同的编程语言——JavaScript。

好了,废话不多说,让我们开始吧!🚀

1. 项目初始化

1.1 安装 Node.js 和 npm

首先,确保你已经安装了 Node.js 和 npm(Node Package Manager)。你可以通过以下命令检查是否已经安装:

node -v
npm -v

如果还没有安装,可以从 Node.js 官方网站 下载并安装最新版本。安装完成后,再次运行上述命令,确保版本号正确显示。

1.2 创建项目目录

接下来,创建一个新的项目目录,并进入该目录:

mkdir task-manager-api
cd task-manager-api

1.3 初始化项目

在项目目录中,使用 npm init 命令初始化一个新的 Node.js 项目。这个命令会生成一个 package.json 文件,用于管理项目的依赖和配置。

npm init -y

-y 参数会自动接受所有默认设置,省去手动输入的麻烦。如果你想自定义项目信息,可以去掉 -y 参数,然后根据提示输入相关信息。

1.4 安装必要的依赖

为了构建我们的任务管理应用,我们需要安装一些常用的依赖包。以下是几个必备的包:

  • Express:一个轻量级的 Web 框架,用于快速构建 RESTful API。
  • Mongoose:一个 MongoDB 对象建模工具,用于简化与 MongoDB 数据库的交互。
  • dotenv:用于加载环境变量,方便管理和保护敏感信息。
  • bcrypt:用于加密用户密码,确保数据安全。
  • jsonwebtoken:用于生成和验证 JWT(JSON Web Token),实现用户认证。
  • validator:用于验证用户输入的数据格式。

安装这些依赖包:

npm install express mongoose dotenv bcrypt jsonwebtoken validator

1.5 配置环境变量

为了保护敏感信息(如数据库连接字符串、JWT 密钥等),我们应该将这些信息存储在环境变量中,而不是直接写在代码里。为此,我们可以使用 dotenv 包来加载 .env 文件中的环境变量。

首先,在项目根目录下创建一个 .env 文件,并添加以下内容:

PORT=3000
MONGO_URI=mongodb://localhost:27017/task-manager
JWT_SECRET=your_jwt_secret_key

然后,在项目的入口文件(通常是 index.jsapp.js)中,引入 dotenv 并加载环境变量:

require('dotenv').config();

1.6 创建项目结构

为了让项目更加模块化和易于维护,我们可以按照以下结构组织代码:

task-manager-api/
│
├── config/                # 配置文件
│   └── db.js              # 数据库连接配置
│
├── models/                # 数据模型
│   └── User.js            # 用户模型
│   └── Task.js            # 任务模型
│
├── routes/                # 路由文件
│   └── auth.js            # 用户认证路由
│   └── tasks.js           # 任务管理路由
│
├── middleware/            # 中间件
│   └── auth.js            # 认证中间件
│
├── utils/                 # 工具函数
│   └── sendEmail.js       # 发送邮件工具
│
├── .env                   # 环境变量文件
├── package.json           # 项目依赖和配置
└── index.js               # 项目入口文件

2. 设置数据库连接

2.1 安装 MongoDB

我们选择 MongoDB 作为任务管理应用的数据库。MongoDB 是一个 NoSQL 数据库,适合存储灵活的文档数据。你可以通过以下方式安装 MongoDB:

  • Windows:从 MongoDB 官方网站 下载并安装 MongoDB 社区版。

  • macOS:使用 Homebrew 安装:

    brew tap mongodb/brew
    brew install mongodb-community@6.0
  • Linux:根据你的发行版,参考官方文档进行安装。

安装完成后,启动 MongoDB 服务:

mongod

2.2 连接 MongoDB

接下来,我们编写代码来连接 MongoDB 数据库。在 config/db.js 文件中,使用 Mongoose 连接到 MongoDB:

const mongoose = require('mongoose');
const { MONGO_URI } = process.env;

const connectDB = async () => {
  try {
    await mongoose.connect(MONGO_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });
    console.log('MongoDB connected successfully!');
  } catch (error) {
    console.error('Failed to connect to MongoDB:', error.message);
    process.exit(1); // 如果连接失败,退出进程
  }
};

module.exports = connectDB;

2.3 在入口文件中引入数据库连接

index.js 文件中,引入并调用 connectDB 函数,确保在应用启动时连接到 MongoDB:

require('dotenv').config();
const express = require('express');
const connectDB = require('./config/db');

// 连接数据库
connectDB();

// 创建 Express 应用
const app = express();

// 其他配置...

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

3. 创建数据模型

3.1 用户模型

models/User.js 文件中,定义用户模型。我们将为用户添加一些基本字段,如用户名、电子邮件和密码。为了确保密码的安全性,我们将使用 bcrypt 对密码进行加密。

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

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    lowercase: true,
    validate(value) {
      if (!validator.isEmail(value)) {
        throw new Error('Invalid email address');
      }
    },
  },
  password: {
    type: String,
    required: true,
    minlength: 6,
    trim: true,
  },
  tokens: [
    {
      token: {
        type: String,
        required: true,
      },
    },
  ],
});

// 在保存用户之前,加密密码
userSchema.pre('save', async function (next) {
  const user = this;

  if (user.isModified('password')) {
    user.password = await bcrypt.hash(user.password, 8);
  }

  next();
});

// 生成 JWT 令牌
userSchema.methods.generateAuthToken = async function () {
  const user = this;
  const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET);

  user.tokens = user.tokens.concat({ token });
  await user.save();

  return token;
};

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

module.exports = User;

3.2 任务模型

models/Task.js 文件中,定义任务模型。每个任务将与一个用户关联,并包含任务的标题、描述和完成状态。

const mongoose = require('mongoose');
const { default: validator } = require('validator');

const taskSchema = new mongoose.Schema(
  {
    description: {
      type: String,
      required: true,
      trim: true,
    },
    completed: {
      type: Boolean,
      default: false,
    },
    owner: {
      type: mongoose.Schema.Types.ObjectId,
      required: true,
      ref: 'User',
    },
  },
  {
    timestamps: true,
  }
);

const Task = mongoose.model('Task', taskSchema);

module.exports = Task;

4. 实现用户认证

4.1 注册用户

routes/auth.js 文件中,实现用户注册功能。我们将使用 express 的路由功能来处理 HTTP 请求,并使用 bcrypt 加密用户密码。

const express = require('express');
const User = require('../models/User');
const router = new express.Router();

router.post('/users', async (req, res) => {
  try {
    const user = new User(req.body);
    await user.save();
    const token = await user.generateAuthToken();
    res.status(201).send({ user, token });
  } catch (error) {
    res.status(400).send(error);
  }
});

module.exports = router;

4.2 登录用户

接下来,实现用户登录功能。我们将验证用户的电子邮件和密码,并生成一个 JWT 令牌作为响应。

router.post('/users/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await User.findByCredentials(email, password);
    const token = await user.generateAuthToken();
    res.send({ user, token });
  } catch (error) {
    res.status(400).send();
  }
});

// 验证用户凭据
User.statics.findByCredentials = async (email, password) => {
  const user = await User.findOne({ email });

  if (!user) {
    throw new Error('Unable to login');
  }

  const isMatch = await bcrypt.compare(password, user.password);

  if (!isMatch) {
    throw new Error('Unable to login');
  }

  return user;
};

4.3 认证中间件

为了保护某些路由,确保只有经过身份验证的用户才能访问,我们需要编写一个认证中间件。这个中间件将从请求头中提取 JWT 令牌,并验证其有效性。

middleware/auth.js 文件中,编写认证中间件:

const jwt = require('jsonwebtoken');
const User = require('../models/User');

const auth = async (req, res, next) => {
  try {
    const token = req.header('Authorization').replace('Bearer ', '');
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const user = await User.findOne({ _id: decoded._id, 'tokens.token': token });

    if (!user) {
      throw new Error();
    }

    req.token = token;
    req.user = user;
    next();
  } catch (error) {
    res.status(401).send({ error: 'Please authenticate.' });
  }
};

module.exports = auth;

4.4 保护路由

现在,我们可以使用 auth 中间件来保护某些路由。例如,保护 /users/me 路由,只有经过身份验证的用户才能访问自己的信息。

router.get('/users/me', auth, async (req, res) => {
  res.send(req.user);
});

5. 实现任务管理功能

5.1 创建任务

routes/tasks.js 文件中,实现创建任务的功能。我们将使用 auth 中间件来确保只有经过身份验证的用户才能创建任务。

const express = require('express');
const Task = require('../models/Task');
const auth = require('../middleware/auth');
const router = new express.Router();

router.post('/tasks', auth, async (req, res) => {
  const task = new Task({
    ...req.body,
    owner: req.user._id,
  });

  try {
    await task.save();
    res.status(201).send(task);
  } catch (error) {
    res.status(400).send(error);
  }
});

5.2 获取任务列表

接下来,实现获取任务列表的功能。我们将允许用户查看自己创建的所有任务,并提供分页和筛选功能。

router.get('/tasks', auth, async (req, res) => {
  const match = {};
  const sort = {};

  if (req.query.completed) {
    match.completed = req.query.completed === 'true';
  }

  if (req.query.sortBy) {
    const parts = req.query.sortBy.split(':');
    sort[parts[0]] = parts[1] === 'desc' ? -1 : 1;
  }

  try {
    await req.user.populate({
      path: 'tasks',
      match,
      options: {
        limit: parseInt(req.query.limit),
        skip: parseInt(req.query.skip),
        sort,
      },
    }).execPopulate();
    res.send(req.user.tasks);
  } catch (error) {
    res.status(500).send();
  }
});

5.3 更新任务

实现更新任务的功能。用户可以通过 PATCH 请求更新任务的部分字段,例如完成状态或描述。

router.patch('/tasks/:id', auth, async (req, res) => {
  const updates = Object.keys(req.body);
  const allowedUpdates = ['description', 'completed'];
  const isValidOperation = updates.every((update) =>
    allowedUpdates.includes(update)
  );

  if (!isValidOperation) {
    return res.status(400).send({ error: 'Invalid updates!' });
  }

  try {
    const task = await Task.findOne({
      _id: req.params.id,
      owner: req.user._id,
    });

    if (!task) {
      return res.status(404).send();
    }

    updates.forEach((update) => (task[update] = req.body[update]));
    await task.save();

    res.send(task);
  } catch (error) {
    res.status(400).send(error);
  }
});

5.4 删除任务

最后,实现删除任务的功能。用户可以通过 DELETE 请求删除指定的任务。

router.delete('/tasks/:id', auth, async (req, res) => {
  try {
    const task = await Task.findOneAndDelete({
      _id: req.params.id,
      owner: req.user._id,
    });

    if (!task) {
      return res.status(404).send();
    }

    res.send(task);
  } catch (error) {
    res.status(500).send();
  }
});

6. 测试 API

6.1 使用 Postman 测试

为了测试我们刚刚实现的 API,可以使用 Postman 或其他类似的工具。Postman 是一个强大的 API 测试工具,支持发送各种类型的 HTTP 请求,并查看响应结果。

6.1.1 注册用户

  • URL: POST /users
  • Body:
    {
    "name": "John Doe",
    "email": "john@example.com",
    "password": "password123"
    }

6.1.2 登录用户

  • URL: POST /users/login
  • Body:
    {
    "email": "john@example.com",
    "password": "password123"
    }

6.1.3 创建任务

  • URL: POST /tasks
  • Headers:
    Authorization: Bearer <token>
  • Body:
    {
    "description": "Complete the project report"
    }

6.1.4 获取任务列表

  • URL: GET /tasks
  • Headers:
    Authorization: Bearer <token>

6.1.5 更新任务

  • URL: PATCH /tasks/:id
  • Headers:
    Authorization: Bearer <token>
  • Body:
    {
    "completed": true
    }

6.1.6 删除任务

  • URL: DELETE /tasks/:id
  • Headers:
    Authorization: Bearer <token>

6.2 使用 cURL 测试

如果你更喜欢使用命令行工具,可以使用 cURL 来测试 API。以下是一些常见的 cURL 命令示例:

6.2.1 注册用户

curl -X POST http://localhost:3000/users 
  -H "Content-Type: application/json" 
  -d '{"name": "John Doe", "email": "john@example.com", "password": "password123"}'

6.2.2 登录用户

curl -X POST http://localhost:3000/users/login 
  -H "Content-Type: application/json" 
  -d '{"email": "john@example.com", "password": "password123"}'

6.2.3 创建任务

curl -X POST http://localhost:3000/tasks 
  -H "Authorization: Bearer <token>" 
  -H "Content-Type: application/json" 
  -d '{"description": "Complete the project report"}'

6.2.4 获取任务列表

curl -X GET http://localhost:3000/tasks 
  -H "Authorization: Bearer <token>"

6.2.5 更新任务

curl -X PATCH http://localhost:3000/tasks/<id> 
  -H "Authorization: Bearer <token>" 
  -H "Content-Type: application/json" 
  -d '{"completed": true}'

6.2.6 删除任务

curl -X DELETE http://localhost:3000/tasks/<id> 
  -H "Authorization: Bearer <token>"

7. 部署应用

7.1 选择部署平台

现在,我们的任务管理应用已经开发完成,接下来是部署阶段。可以选择多种云服务平台来部署 Node.js 应用,例如:

  • Heroku:一个简单易用的 PaaS 平台,适合快速部署小型应用。
  • AWS Elastic Beanstalk:亚马逊提供的托管服务,支持自动扩展和负载均衡。
  • DigitalOcean:一个性价比高的云服务平台,适合中小型应用。
  • Vercel:专注于静态站点和无服务器函数的部署平台,也支持 Node.js 应用。

7.2 使用 Heroku 部署

我们将使用 Heroku 来部署应用。首先,确保你已经安装了 Heroku CLI,并登录到你的 Heroku 账户:

heroku login

接下来,创建一个新的 Heroku 应用:

heroku create your-app-name

将应用推送到 Heroku 远程仓库:

git push heroku main

最后,设置环境变量。由于 Heroku 不会读取本地的 .env 文件,我们需要手动设置环境变量:

heroku config:set MONGO_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/task-manager
heroku config:set JWT_SECRET=your_jwt_secret_key

7.3 使用 Docker 部署

如果你更喜欢使用 Docker 来部署应用,可以编写一个 Dockerfile 来容器化你的应用。以下是一个简单的 Dockerfile 示例:

# 使用官方 Node.js 镜像作为基础镜像
FROM node:14

# 设置工作目录
WORKDIR /usr/src/app

# 复制 package.json 和 package-lock.json
COPY package*.json ./

# 安装依赖
RUN npm install

# 复制应用代码
COPY . .

# 暴露应用端口
EXPOSE 3000

# 启动应用
CMD ["npm", "start"]

构建并推送 Docker 镜像:

docker build -t your-dockerhub-username/task-manager .
docker tag your-dockerhub-username/task-manager your-dockerhub-username/task-manager:latest
docker push your-dockerhub-username/task-manager:latest

7.4 监控和日志

无论你选择哪种部署方式,监控和日志记录都是非常重要的。你可以使用以下工具来监控应用的性能和健康状况:

  • Heroku Logs:Heroku 提供了内置的日志功能,可以实时查看应用的日志输出。
  • Papertrail:一个流行的日志管理服务,支持日志聚合和搜索。
  • New Relic:一个全面的应用性能监控工具,可以帮助你跟踪应用的性能瓶颈。
  • Datadog:一个强大的监控和分析平台,支持多种云服务和自定义指标。

结语

恭喜你!通过今天的讲座,我们已经成功地使用 Node.js 开发了一个完整的任务管理应用程序的后端。我们从项目初始化开始,逐步实现了用户认证、任务管理等功能,并最终完成了应用的部署。希望这篇文章对你有所帮助,如果你有任何问题或建议,欢迎随时留言交流。

感谢大家的参与,祝你们编码愉快!🌟

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注