使用 Node.js 开发健康和健身跟踪应用程序的后端

使用 Node.js 开发健康和健身跟踪应用程序的后端

引言

大家好,欢迎来到今天的讲座!今天我们要聊一聊如何使用 Node.js 开发一个健康和健身跟踪应用程序的后端。如果你对健身感兴趣,或者想开发一个帮助人们保持健康的工具,那么这篇文章绝对适合你!我们将从头开始,一步步构建一个完整的后端系统,帮助用户记录他们的健身数据、饮食习惯、睡眠质量等。

在接下来的时间里,我会尽量用轻松诙谐的语言来解释技术细节,确保每个人都能跟上节奏。我们不仅会讨论理论,还会通过实际代码示例来展示如何实现这些功能。准备好了吗?让我们开始吧!

为什么选择 Node.js?

首先,为什么我们要选择 Node.js 来开发这个后端呢?Node.js 是一种基于 JavaScript 的运行时环境,它允许我们在服务器端编写 JavaScript 代码。这有几个好处:

  1. 熟悉的技术栈:如果你已经熟悉前端开发,那么 Node.js 会让你感到非常亲切,因为你可以使用相同的语言(JavaScript)来编写前后端代码。
  2. 异步 I/O:Node.js 使用事件驱动的非阻塞 I/O 模型,这意味着它可以处理大量的并发请求,非常适合需要实时更新的应用程序,比如健身追踪器。
  3. 丰富的生态系统:Node.js 拥有庞大的 npm(Node Package Manager)库,几乎可以找到任何你需要的第三方库或工具,大大减少了开发时间。
  4. 快速迭代:Node.js 的开发速度非常快,尤其是在与现代框架(如 Express 或 NestJS)结合时,可以帮助我们快速构建原型并进行迭代。

总之,Node.js 是一个非常灵活且强大的工具,特别适合构建像健康和健身跟踪这样的应用,因为它可以轻松处理大量数据,并提供实时反馈。

项目结构

在我们开始编写代码之前,先来了解一下项目的整体结构。一个好的项目结构可以让我们的代码更加模块化、易于维护。以下是一个典型的 Node.js 项目结构:

/fitness-tracker-backend
│
├── /config
│   └── db.js                  # 数据库配置文件
│
├── /controllers
│   ├── authController.js      # 用户认证控制器
│   ├── userController.js      # 用户管理控制器
│   ├── workoutController.js   # 健身记录控制器
│   └── dietController.js      # 饮食记录控制器
│
├── /models
│   ├── User.js                # 用户模型
│   ├── Workout.js             # 健身记录模型
│   └── Diet.js                # 饮食记录模型
│
├── /routes
│   ├── authRoutes.js          # 用户认证路由
│   ├── userRoutes.js          # 用户管理路由
│   ├── workoutRoutes.js       # 健身记录路由
│   └── dietRoutes.js          # 饮食记录路由
│
├── /services
│   ├── authService.js         # 用户认证服务
│   ├── userService.js         # 用户管理服务
│   └── workoutService.js      # 健身记录服务
│
├── /utils
│   ├── jwt.js                 # JWT 工具函数
│   └── validator.js           # 数据验证工具
│
├── app.js                     # 应用入口文件
└── package.json               # 项目依赖和脚本

项目结构说明

  • /config:存放所有与配置相关的文件,比如数据库连接、环境变量等。
  • /controllers:每个控制器负责处理特定类型的请求。例如,authController.js 处理用户登录和注册的逻辑。
  • /models:定义了数据库中的表结构。每个模型对应一个数据库表,比如 User.js 对应用户表。
  • /routes:定义了 API 的路由。每个路由文件负责处理特定的 HTTP 请求。例如,userRoutes.js 定义了所有与用户相关的 API 端点。
  • /services:服务层包含业务逻辑。它们通常由控制器调用,用于执行复杂的操作,比如验证用户输入、生成 JWT 令牌等。
  • /utils:存放一些通用的工具函数,比如 JWT 解析、数据验证等。
  • app.js:这是整个应用的入口文件,负责启动服务器并加载所有的路由和中间件。
  • package.json:定义了项目的依赖和脚本。

设置项目

初始化项目

首先,我们需要创建一个新的 Node.js 项目。打开终端,导航到你想要创建项目的目录,然后运行以下命令:

mkdir fitness-tracker-backend
cd fitness-tracker-backend
npm init -y

这将创建一个新的项目,并生成一个 package.json 文件,其中包含了项目的默认配置。

安装依赖

接下来,我们需要安装一些必要的依赖包。我们将使用以下工具:

  • Express:一个轻量级的 Node.js 框架,用于构建 Web 应用程序。
  • Mongoose:一个 MongoDB ORM(对象关系映射)库,用于与 MongoDB 数据库交互。
  • bcryptjs:用于加密用户密码。
  • jsonwebtoken:用于生成和验证 JWT(JSON Web Token),以便实现用户认证。
  • dotenv:用于管理环境变量。
  • validator:用于验证用户输入的数据。

运行以下命令来安装这些依赖:

npm install express mongoose bcryptjs jsonwebtoken dotenv validator

配置环境变量

为了确保敏感信息(如数据库连接字符串、JWT 密钥等)不会泄露,我们应该将它们放在环境变量中。我们可以使用 dotenv 来加载这些变量。

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

PORT=5000
MONGO_URI=mongodb://localhost:27017/fitnessTrackerDB
JWT_SECRET=mysecretkey

然后,在 app.js 中引入 dotenv 并加载环境变量:

require('dotenv').config();

连接数据库

我们将使用 MongoDB 作为数据库。MongoDB 是一个 NoSQL 数据库,非常适合存储非结构化数据,比如用户的健身记录和饮食习惯。

首先,确保你已经在本地或云上安装并运行了 MongoDB。然后,在 /config/db.js 中编写代码来连接数据库:

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.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;

最后,在 app.js 中导入并调用 connectDB 函数:

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

// 连接数据库
connectDB();

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

现在,当你启动应用程序时,它会自动连接到 MongoDB 数据库。

用户认证

为了让用户能够安全地访问他们的健身数据,我们需要实现用户认证功能。我们将使用 JWT(JSON Web Token)来实现这一目标。JWT 是一种轻量级的认证机制,它允许我们在不依赖会话的情况下验证用户身份。

创建用户模型

首先,我们需要在 /models/User.js 中定义用户模型。这个模型将存储用户的基本信息,比如用户名、电子邮件和密码。

const mongoose = require('mongoose');
const { Schema } = mongoose;

const userSchema = new Schema({
  name: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('User', userSchema);

注册用户

接下来,我们来实现用户注册的功能。用户注册时,我们需要验证他们提供的信息是否有效,然后将他们的密码加密并保存到数据库中。

/controllers/authController.js 中编写注册逻辑:

const User = require('../models/User');
const bcrypt = require('bcryptjs');
const { validationResult } = require('express-validator');

exports.registerUser = async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const { name, email, password } = req.body;

  try {
    // 检查用户是否已存在
    let user = await User.findOne({ email });
    if (user) {
      return res.status(400).json({ msg: 'User already exists' });
    }

    // 加密密码
    const salt = await bcrypt.genSalt(10);
    const hashedPassword = await bcrypt.hash(password, salt);

    // 创建新用户
    user = new User({
      name,
      email,
      password: hashedPassword,
    });

    await user.save();

    // 返回成功响应
    res.json({ msg: 'User registered successfully' });
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

登录用户

用户注册后,他们可以通过提供电子邮件和密码来登录。我们将使用 JWT 生成一个令牌,并将其返回给客户端。客户端可以在后续请求中使用这个令牌来验证用户身份。

/controllers/authController.js 中编写登录逻辑:

const jwt = require('jsonwebtoken');
const config = require('config');

exports.loginUser = async (req, res) => {
  const { email, password } = req.body;

  try {
    // 查找用户
    let user = await User.findOne({ email });
    if (!user) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    // 验证密码
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    // 生成 JWT
    const payload = {
      user: {
        id: user.id,
      },
    };

    jwt.sign(
      payload,
      process.env.JWT_SECRET,
      { expiresIn: '1h' },
      (err, token) => {
        if (err) throw err;
        res.json({ token });
      }
    );
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

保护路由

为了让某些路由只能被已登录的用户访问,我们需要实现一个中间件来验证 JWT 令牌。如果令牌有效,我们将允许用户访问该路由;否则,返回 401 错误。

/middleware/auth.js 中编写中间件:

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

const authenticate = async (req, res, next) => {
  const token = req.header('x-auth-token');

  if (!token) {
    return res.status(401).json({ msg: 'No token, authorization denied' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = await User.findById(decoded.user.id).select('-password');
    next();
  } catch (error) {
    res.status(401).json({ msg: 'Token is not valid' });
  }
};

module.exports = authenticate;

定义认证路由

现在,我们来定义用户注册和登录的路由。在 /routes/authRoutes.js 中编写以下代码:

const express = require('express');
const router = express.Router();
const { check } = require('express-validator');
const { registerUser, loginUser } = require('../controllers/authController');

// @route   POST api/auth/register
// @desc    Register a new user
// @access  Public
router.post(
  '/register',
  [
    check('name', 'Name is required').not().isEmpty(),
    check('email', 'Please include a valid email').isEmail(),
    check('password', 'Password must be at least 6 characters').isLength({ min: 6 }),
  ],
  registerUser
);

// @route   POST api/auth/login
// @desc    Login user and get token
// @access  Public
router.post(
  '/login',
  [
    check('email', 'Please include a valid email').isEmail(),
    check('password', 'Password is required').exists(),
  ],
  loginUser
);

module.exports = router;

最后,在 app.js 中导入并使用这些路由:

const authRoutes = require('./routes/authRoutes');

app.use('/api/auth', authRoutes);

用户管理

除了认证功能,我们还需要为用户提供一些基本的管理功能,比如获取用户信息、更新个人资料等。

获取用户信息

/controllers/userController.js 中编写获取用户信息的逻辑:

exports.getUser = async (req, res) => {
  try {
    const user = await User.findById(req.user.id).select('-password');
    res.json(user);
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

更新用户信息

我们还可以允许用户更新他们的个人资料。在 /controllers/userController.js 中编写更新逻辑:

exports.updateUser = async (req, res) => {
  const { name, email } = req.body;

  // 构建用户对象
  const userFields = {};
  if (name) userFields.name = name;
  if (email) userFields.email = email;

  try {
    let user = await User.findById(req.user.id);

    if (!user) {
      return res.status(404).json({ msg: 'User not found' });
    }

    // 更新用户信息
    user = await User.findByIdAndUpdate(
      req.user.id,
      { $set: userFields },
      { new: true }
    );

    res.json(user);
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

定义用户管理路由

/routes/userRoutes.js 中定义用户管理的路由:

const express = require('express');
const router = express.Router();
const { getUser, updateUser } = require('../controllers/userController');
const authenticate = require('../middleware/auth');

// @route   GET api/users/me
// @desc    Get current user's info
// @access  Private
router.get('/me', authenticate, getUser);

// @route   PUT api/users/me
// @desc    Update current user's info
// @access  Private
router.put('/me', authenticate, updateUser);

module.exports = router;

最后,在 app.js 中导入并使用这些路由:

const userRoutes = require('./routes/userRoutes');

app.use('/api/users', userRoutes);

健身记录

现在,我们已经实现了用户认证和管理功能,接下来让我们为用户添加健身记录功能。用户可以记录他们的锻炼活动,比如跑步、举重等。

创建健身记录模型

/models/Workout.js 中定义健身记录模型:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const workoutSchema = new Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true,
  },
  date: {
    type: Date,
    default: Date.now,
  },
  exercises: [
    {
      name: {
        type: String,
        required: true,
      },
      sets: {
        type: Number,
        required: true,
      },
      reps: {
        type: Number,
        required: true,
      },
      weight: {
        type: Number,
        required: true,
      },
    },
  ],
});

module.exports = mongoose.model('Workout', workoutSchema);

添加健身记录

/controllers/workoutController.js 中编写添加健身记录的逻辑:

exports.addWorkout = async (req, res) => {
  const { exercises } = req.body;

  try {
    const workout = new Workout({
      user: req.user.id,
      exercises,
    });

    await workout.save();
    res.json(workout);
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

获取健身记录

我们还需要为用户提供查看他们健身记录的功能。在 /controllers/workoutController.js 中编写获取健身记录的逻辑:

exports.getWorkouts = async (req, res) => {
  try {
    const workouts = await Workout.find({ user: req.user.id }).sort('-date');
    res.json(workouts);
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

定义健身记录路由

/routes/workoutRoutes.js 中定义健身记录的路由:

const express = require('express');
const router = express.Router();
const { addWorkout, getWorkouts } = require('../controllers/workoutController');
const authenticate = require('../middleware/auth');

// @route   POST api/workouts
// @desc    Add a new workout
// @access  Private
router.post('/', authenticate, addWorkout);

// @route   GET api/workouts
// @desc    Get all workouts for the current user
// @access  Private
router.get('/', authenticate, getWorkouts);

module.exports = router;

最后,在 app.js 中导入并使用这些路由:

const workoutRoutes = require('./routes/workoutRoutes');

app.use('/api/workouts', workoutRoutes);

饮食记录

除了健身记录,我们还可以为用户提供饮食记录功能。用户可以记录他们每天摄入的食物,帮助他们更好地管理饮食。

创建饮食记录模型

/models/Diet.js 中定义饮食记录模型:

const mongoose = require('mongoose');
const { Schema } = mongoose;

const dietSchema = new Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true,
  },
  date: {
    type: Date,
    default: Date.now,
  },
  meals: [
    {
      name: {
        type: String,
        required: true,
      },
      calories: {
        type: Number,
        required: true,
      },
      protein: {
        type: Number,
        required: true,
      },
      carbs: {
        type: Number,
        required: true,
      },
      fat: {
        type: Number,
        required: true,
      },
    },
  ],
});

module.exports = mongoose.model('Diet', dietSchema);

添加饮食记录

/controllers/dietController.js 中编写添加饮食记录的逻辑:

exports.addDiet = async (req, res) => {
  const { meals } = req.body;

  try {
    const diet = new Diet({
      user: req.user.id,
      meals,
    });

    await diet.save();
    res.json(diet);
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

获取饮食记录

我们还需要为用户提供查看他们饮食记录的功能。在 /controllers/dietController.js 中编写获取饮食记录的逻辑:

exports.getDiet = async (req, res) => {
  try {
    const diets = await Diet.find({ user: req.user.id }).sort('-date');
    res.json(diets);
  } catch (error) {
    console.error(error.message);
    res.status(500).send('Server error');
  }
};

定义饮食记录路由

/routes/dietRoutes.js 中定义饮食记录的路由:

const express = require('express');
const router = express.Router();
const { addDiet, getDiet } = require('../controllers/dietController');
const authenticate = require('../middleware/auth');

// @route   POST api/diets
// @desc    Add a new diet record
// @access  Private
router.post('/', authenticate, addDiet);

// @route   GET api/diets
// @desc    Get all diet records for the current user
// @access  Private
router.get('/', authenticate, getDiet);

module.exports = router;

最后,在 app.js 中导入并使用这些路由:

const dietRoutes = require('./routes/dietRoutes');

app.use('/api/diets', dietRoutes);

总结

恭喜你!我们已经成功构建了一个健康和健身跟踪应用程序的后端。通过这个项目,你学会了如何使用 Node.js 和 Express 来创建 RESTful API,如何使用 Mongoose 与 MongoDB 交互,以及如何实现用户认证和授权功能。此外,我们还为用户提供了健身记录和饮食记录的功能,帮助他们更好地管理自己的健康。

当然,这只是一个基础版本的应用程序。你可以根据需求进一步扩展它,比如添加更多的健身活动类型、集成第三方 API(如天气预报或营养数据库)、实现社交功能(如分享健身成果)等。

希望这篇文章对你有所帮助!如果你有任何问题或建议,欢迎随时留言交流 😊

祝你编码愉快,保持健康!💪

发表回复

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