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 全栈应用。这个应用实现了用户注册、登录、任务管理等功能。未来可以进一步扩展和优化,例如:

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

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

相关推荐
学不会•42 分钟前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS2 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
Theodore_10222 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
活宝小娜3 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点3 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow3 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o3 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
----云烟----4 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024064 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic4 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端