Node.js + MongoDB + Vue 3 全栈应用项目开发

​🌈个人主页:前端青山

🔥系列专栏:node.js篇

🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来node.js篇专栏内容:Node.js + MongoDB + Vue 3 全栈应用项目开发

在前几篇文章中,我们已经为 Node.js 应用添加了身份验证、CORS 配置、缓存机制、性能监控、限流功能和日志优化。本文将继续在这个基础上,逐步构建一个完整的 Node.js + MongoDB + Vue 3 全栈应用。我们将从项目结构设计、前后端交互、数据模型设计等方面入手,逐步实现一个功能完善的全栈应用。

目录

[1. 项目概述](#1. 项目概述)

[2. 项目结构设计](#2. 项目结构设计)

[3. 数据模型设计](#3. 数据模型设计)

[3.1 用户模型](#3.1 用户模型)

[3.2 任务模型](#3.2 任务模型)

[4. 后端 API 开发](#4. 后端 API 开发)

[4.1 用户模块](#4.1 用户模块)

[4.1.1 用户注册](#4.1.1 用户注册)

[4.1.2 用户登录](#4.1.2 用户登录)

[4.1.3 获取用户信息](#4.1.3 获取用户信息)

[4.2 项目模块](#4.2 项目模块)

[4.2.1 创建任务](#4.2.1 创建任务)

[4.2.2 获取任务列表](#4.2.2 获取任务列表)

[4.2.3 更新任务](#4.2.3 更新任务)

[4.2.4 删除任务](#4.2.4 删除任务)

[5. 前端 Vue 3 应用开发](#5. 前端 Vue 3 应用开发)

[5.1 项目初始化](#5.1 项目初始化)

[5.2 组件开发](#5.2 组件开发)

[5.2.1 登录组件](#5.2.1 登录组件)

[5.2.2 注册组件](#5.2.2 注册组件)

[5.2.3 任务列表组件](#5.2.3 任务列表组件)

[5.3 状态管理](#5.3 状态管理)

[6. 前后端联调](#6. 前后端联调)

[6.1 路由配置](#6.1 路由配置)

[6.2 主页组件](#6.2 主页组件)

[6.3 登录和注册页面](#6.3 登录和注册页面)

[7. 部署与测试](#7. 部署与测试)

[7.1 部署后端](#7.1 部署后端)

[7.2 部署前端](#7.2 部署前端)

[7.3 测试](#7.3 测试)

[8. 总结与展望](#8. 总结与展望)

1. 项目概述

我们的目标是构建一个简单的任务管理应用,用户可以注册、登录、创建和管理任务。应用的主要功能包括:

  • 用户注册和登录
  • 用户信息管理
  • 任务创建、编辑和删除
  • 任务列表展示

2. 项目结构设计

为了保持项目的清晰和可维护性,我们将项目分为前后端两部分。项目结构如下:

javascript 复制代码
task-manager/
├── backend/
│   ├── node_modules/
│   ├── src/
│   │   ├── controllers/
│   │   ├── models/
│   │   ├── routes/
│   │   ├── middlewares/
│   │   ├── config/
│   │   ├── app.js
│   │   ├── server.js
│   ├── .env
│   ├── package.json
│   └── README.md
├── frontend/
│   ├── node_modules/
│   ├── public/
│   │   └── index.html
│   ├── src/
│   │   ├── assets/
│   │   ├── components/
│   │   ├── views/
│   │   ├── store/
│   │   ├── router/
│   │   ├── App.vue
│   │   └── main.js
│   ├── .env
│   ├── package.json
│   └── README.md
├── .gitignore
└── README.md

3. 数据模型设计

我们将使用 MongoDB 作为数据库,定义两个主要的数据模型:用户和任务。

3.1 用户模型

用户模型包含以下字段:

  • username:用户名
  • email:邮箱
  • password:密码(加密存储)
  • createdAt:创建时间
  • updatedAt:更新时间

backend/src/models/user.js 中定义用户模型:

javascript 复制代码
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

UserSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

UserSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

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

module.exports = User;
3.2 任务模型

任务模型包含以下字段:

  • title:任务标题
  • description:任务描述
  • status:任务状态(未完成、已完成)
  • userId:关联用户 ID
  • createdAt:创建时间
  • updatedAt:更新时间

backend/src/models/task.js 中定义任务模型:

javascript 复制代码
const mongoose = require('mongoose');

const TaskSchema = new mongoose.Schema({
  title: { type: String, required: true },
  description: { type: String, required: true },
  status: { type: String, enum: ['未完成', '已完成'], default: '未完成' },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

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

module.exports = Task;

4. 后端 API 开发

4.1 用户模块
4.1.1 用户注册

backend/src/controllers/userController.js 中实现用户注册功能:

javascript 复制代码
const User = require('../models/user');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

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

  try {
    let user = await User.findOne({ email });
    if (user) {
      return res.status(400).json({ msg: 'User already exists' });
    }

    user = new User({ username, email, password });
    await user.save();

    const payload = { user: { id: user.id } };
    const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });

    res.status(201).json({ token });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};
4.1.2 用户登录

backend/src/controllers/userController.js 中实现用户登录功能:

javascript 复制代码
exports.login = 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 user.comparePassword(password);
    if (!isMatch) {
      return res.status(400).json({ msg: 'Invalid credentials' });
    }

    const payload = { user: { id: user.id } };
    const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });

    res.json({ token });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};
4.1.3 获取用户信息

backend/src/controllers/userController.js 中实现获取用户信息功能:

javascript 复制代码
exports.getUser = async (req, res) => {
  try {
    const user = await User.findById(req.user.id).select('-password');
    if (!user) {
      return res.status(404).json({ msg: 'User not found' });
    }

    res.json(user);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};
4.2 项目模块
4.2.1 创建任务

backend/src/controllers/taskController.js 中实现创建任务功能:

javascript 复制代码
const Task = require('../models/task');

exports.createTask = async (req, res) => {
  const { title, description } = req.body;

  try {
    const task = new Task({ title, description, userId: req.user.id });
    await task.save();

    res.status(201).json(task);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};
4.2.2 获取任务列表

backend/src/controllers/taskController.js 中实现获取任务列表功能:

javascript 复制代码
exports.getTasks = async (req, res) => {
  try {
    const tasks = await Task.find({ userId: req.user.id }).sort({ createdAt: -1 });
    res.json(tasks);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};
4.2.3 更新任务

backend/src/controllers/taskController.js 中实现更新任务功能:

javascript 复制代码
exports.updateTask = async (req, res) => {
  const { title, description, status } = req.body;

  try {
    let task = await Task.findById(req.params.id);
    if (!task) {
      return res.status(404).json({ msg: 'Task not found' });
    }

    if (task.userId.toString() !== req.user.id) {
      return res.status(401).json({ msg: 'Not authorized' });
    }

    task.title = title;
    task.description = description;
    task.status = status;

    await task.save();

    res.json(task);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};
4.2.4 删除任务

backend/src/controllers/taskController.js 中实现删除任务功能:

javascript 复制代码
exports.deleteTask = async (req, res) => {
  try {
    let task = await Task.findById(req.params.id);
    if (!task) {
      return res.status(404).json({ msg: 'Task not found' });
    }

    if (task.userId.toString() !== req.user.id) {
      return res.status(401).json({ msg: 'Not authorized' });
    }

    await task.remove();

    res.json({ msg: 'Task deleted' });
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server error');
  }
};

5. 前端 Vue 3 应用开发

5.1 项目初始化

首先,我们需要初始化 Vue 3 项目。打开终端,导航到 frontend 目录,然后运行以下命令:

bash 复制代码
npm init vue@latest

按照提示完成项目初始化。安装完成后,进入项目目录并安装依赖:

javascript 复制代码
cd frontend npm install
5.2 组件开发
5.2.1 登录组件

frontend/src/components/Login.vue 中创建登录组件:

javascript 复制代码
<template>
  <div class="login">
    <h2>Login</h2>
    <form @submit.prevent="handleLogin">
      <div class="form-group">
        <label for="email">Email</label>
        <input type="email" v-model="email" required />
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <input type="password" v-model="password" required />
      </div>
      <button type="submit">Login</button>
    </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      email: '',
      password: ''
    };
  },
  methods: {
    async handleLogin() {
      try {
        const response = await axios.post('http://localhost:3000/auth/login', {
          email: this.email,
          password: this.password
        });
        localStorage.setItem('token', response.data.token);
        this.$router.push('/tasks');
      } catch (error) {
        alert('Login failed');
      }
    }
  }
};
</script>

<style scoped>
/* 添加一些样式 */
</style>
5.2.2 注册组件

frontend/src/components/Register.vue 中创建注册组件:

javascript 复制代码
<template>
  <div class="register">
    <h2>Register</h2>
    <form @submit.prevent="handleRegister">
      <div class="form-group">
        <label for="username">Username</label>
        <input type="text" v-model="username" required />
      </div>
      <div class="form-group">
        <label for="email">Email</label>
        <input type="email" v-model="email" required />
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <input type="password" v-model="password" required />
      </div>
      <button type="submit">Register</button>
    </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      username: '',
      email: '',
      password: ''
    };
  },
  methods: {
    async handleRegister() {
      try {
        await axios.post('http://localhost:3000/auth/register', {
          username: this.username,
          email: this.email,
          password: this.password
        });
        this.$router.push('/login');
      } catch (error) {
        alert('Registration failed');
      }
    }
  }
};
</script>

<style scoped>
/* 添加一些样式 */
</style>
5.2.3 任务列表组件

frontend/src/components/TaskList.vue 中创建任务列表组件:

javascript 复制代码
<template>
  <div class="task-list">
    <h2>Tasks</h2>
    <ul>
      <li v-for="task in tasks" :key="task._id">
        <span>{{ task.title }}</span>
        <button @click="deleteTask(task._id)">Delete</button>
      </li>
    </ul>
    <form @submit.prevent="createTask">
      <div class="form-group">
        <label for="title">Title</label>
        <input type="text" v-model="newTask.title" required />
      </div>
      <div class="form-group">
        <label for="description">Description</label>
        <input type="text" v-model="newTask.description" required />
      </div>
      <button type="submit">Create Task</button>
    </form>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      tasks: [],
      newTask: {
        title: '',
        description: ''
      }
    };
  },
  methods: {
    async fetchTasks() {
      const response = await axios.get('http://localhost:3000/tasks', {
        headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
      });
      this.tasks = response.data;
    },
    async createTask() {
      await axios.post('http://localhost:3000/tasks', this.newTask, {
        headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
      });
      this.newTask = { title: '', description: '' };
      this.fetchTasks();
    },
    async deleteTask(id) {
      await axios.delete(`http://localhost:3000/tasks/${id}`, {
        headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
      });
      this.fetchTasks();
    }
  },
  created() {
    this.fetchTasks();
  }
};
</script>

<style scoped>
/* 添加一些样式 */
</style>
5.3 状态管理

为了更好地管理应用的状态,我们将使用 Vuex。首先,安装 Vuex:

bash 复制代码
npm install vuex@next

frontend/src/store/index.js 中创建 Vuex 存储:

javascript 复制代码
import { createStore } from 'vuex';

export default createStore({
  state: {
    token: localStorage.getItem('token') || null
  },
  mutations: {
    setToken(state, token) {
      state.token = token;
      localStorage.setItem('token', token);
    },
    clearToken(state) {
      state.token = null;
      localStorage.removeItem('token');
    }
  },
  actions: {
    login({ commit }, token) {
      commit('setToken', token);
    },
    logout({ commit }) {
      commit('clearToken');
    }
  },
  getters: {
    isAuthenticated: state => !!state.token
  }
});

6. 前后端联调

确保 MongoDB 和后端服务已启动。在 backend 目录下运行以下命令启动后端服务:

bash 复制代码
npm start

frontend 目录下运行以下命令启动前端开发服务器:

bash 复制代码
npm run serve
6.1 路由配置

为了更好地组织应用的路由,我们需要在 frontend/src/router/index.js 中配置路由:

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import Login from '../components/Login.vue';
import Register from '../components/Register.vue';
import TaskList from '../components/TaskList.vue';
import store from '../store';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/login',
    name: 'Login',
    component: Login,
    meta: { requiresAuth: false }
  },
  {
    path: '/register',
    name: 'Register',
    component: Register,
    meta: { requiresAuth: false }
  },
  {
    path: '/tasks',
    name: 'Tasks',
    component: TaskList,
    meta: { requiresAuth: true }
  }
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
});

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !store.getters.isAuthenticated) {
    next('/login');
  } else {
    next();
  }
});

export default router;
6.2 主页组件

frontend/src/views/Home.vue 中创建主页组件:

javascript 复制代码
<template>
  <div class="home">
    <h1>Welcome to the Task Manager</h1>
    <p>Please <router-link to="/login">login</router-link> or <router-link to="/register">register</router-link> to get started.</p>
  </div>
</template>

<script>
export default {
  name: 'Home'
};
</script>

<style scoped>
/* 添加一些样式 */
</style>
6.3 登录和注册页面

frontend/src/App.vue 中设置默认路由:

javascript 复制代码
<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
};
</script>

<style>
/* 添加一些全局样式 */
</style>

7. 部署与测试

7.1 部署后端

将后端应用部署到云服务器或使用 Docker 容器化部署。这里以 Docker 为例,创建 Dockerfile 文件:

javascript 复制代码
# 使用官方 Node.js 运行时镜像
FROM node:14

# 设置工作目录
WORKDIR /app

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

# 安装依赖
RUN npm install

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 3000

# 启动应用
CMD ["node", "src/server.js"]

构建并运行 Docker 容器:

bash 复制代码
docker build -t task-manager-backend .
docker run -d -p 3000:3000 task-manager-backend
7.2 部署前端

将前端应用构建为生产版本,并部署到静态文件服务器。例如,使用 Nginx:

bash 复制代码
npm run build

创建 nginx.conf 文件:

bash 复制代码
server {
  listen 80;
  server_name your-domain.com;

  location / {
    root /path/to/dist;
    try_files $uri $uri/ /index.html;
  }
}

启动 Nginx 服务:

bash 复制代码
sudo nginx -c /path/to/nginx.conf
7.3 测试

确保所有功能正常工作,包括用户注册、登录、任务创建、编辑和删除。可以使用 Postman 或浏览器进行测试。

8. 总结与展望

通过本文,我们成功构建了一个完整的 Node.js + MongoDB + Vue 3 全栈应用。这个应用实现了用户注册、登录、任务管理等功能。未来可以进一步扩展和优化,例如:

  • 添加更多的用户权限管理
  • 实现任务的分类和标签管理
  • 增加实时通知功能
  • 优化前端用户体验

希望本文对你有所帮助,祝你在全栈开发的道路上越走越远!

相关推荐
掘金者阿豪9 分钟前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen29 分钟前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端1 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid2 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger2 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4533 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
lichenyang4533 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174463 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css