首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >全栈开发实战:用MERN构建用户认证与任务管理应用

全栈开发实战:用MERN构建用户认证与任务管理应用

原创
作者头像
IT大佬 jzit-top
发布2026-08-24 15:14:57
发布2026-08-24 15:14:57
590
举报

全栈开发实战:用MERN构建用户认证与任务管理应用

全栈开发要求开发者同时掌握前端、后端和数据库。MERN(MongoDB、Express、React、Node.js)是经典的JavaScript全栈组合,前后端统一语言,开发效率高。本文通过一个带JWT认证的任务管理应用,展示全栈开发的核心流程。

一、技术栈与项目结构

  • MongoDB:文档数据库,使用Mongoose ODM
  • Express:后端框架,提供REST API
  • React:前端框架,使用Hooks和Context管理状态
  • Node.js:运行时,使用jsonwebtoken实现认证

项目结构:

代码语言:javascript
复制
mern-todo/
├── backend/
│   ├── models/User.js
│   ├── models/Task.js
│   ├── middleware/auth.js
│   ├── routes/auth.js
│   ├── routes/tasks.js
│   ├── server.js
│   └── .env
└── frontend/
    ├── src/
    │   ├── api/axios.js
    │   ├── context/AuthContext.js
    │   ├── components/Login.jsx
    │   ├── components/TaskList.jsx
    │   └── App.jsx
    └── package.json

二、后端实现

1. 数据库模型

backend/models/User.js

代码语言:javascript
复制
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.comparePassword = function(candidate) {
  return bcrypt.compare(candidate, this.password);
};

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

backend/models/Task.js

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

const taskSchema = new mongoose.Schema({
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  title: { type: String, required: true },
  completed: { type: Boolean, default: false },
  createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Task', taskSchema);

2. 认证中间件

backend/middleware/auth.js

代码语言:javascript
复制
const jwt = require('jsonwebtoken');

module.exports = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'Access denied' });
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.userId = decoded.userId;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

3. 路由处理

backend/routes/auth.js

代码语言:javascript
复制
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');

router.post('/register', async (req, res) => {
  try {
    const { username, password } = req.body;
    if (!username || !password) return res.status(400).json({ error: 'Missing fields' });
    const exists = await User.findOne({ username });
    if (exists) return res.status(400).json({ error: 'User already exists' });
    const user = new User({ username, password });
    await user.save();
    const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '1d' });
    res.status(201).json({ token, username: user.username });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

router.post('/login', async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = await User.findOne({ username });
    if (!user || !(await user.comparePassword(password))) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '1d' });
    res.json({ token, username: user.username });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;

backend/routes/tasks.js(节选核心CRUD):

代码语言:javascript
复制
const router = require('express').Router();
const auth = require('../middleware/auth');
const Task = require('../models/Task');

// 获取当前用户所有任务
router.get('/', auth, async (req, res) => {
  const tasks = await Task.find({ user: req.userId }).sort('-createdAt');
  res.json(tasks);
});

// 创建任务
router.post('/', auth, async (req, res) => {
  const { title } = req.body;
  if (!title) return res.status(400).json({ error: 'Title required' });
  const task = new Task({ user: req.userId, title });
  await task.save();
  res.status(201).json(task);
});

// 更新任务状态
router.patch('/:id', auth, async (req, res) => {
  const task = await Task.findOneAndUpdate(
    { _id: req.params.id, user: req.userId },
    { completed: req.body.completed },
    { new: true }
  );
  if (!task) return res.status(404).json({ error: 'Task not found' });
  res.json(task);
});

// 删除任务
router.delete('/:id', auth, async (req, res) => {
  const task = await Task.findOneAndDelete({ _id: req.params.id, user: req.userId });
  if (!task) return res.status(404).json({ error: 'Task not found' });
  res.json({ success: true });
});

module.exports = router;

backend/server.js

代码语言:javascript
复制
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();

const app = express();
app.use(cors());
app.use(express.json());

app.use('/api/auth', require('./routes/auth'));
app.use('/api/tasks', require('./routes/tasks'));

mongoose.connect(process.env.MONGO_URI)
  .then(() => app.listen(5000, () => console.log('Server running on port 5000')))
  .catch(err => console.error(err));

三、前端实现

1. Axios实例(自动携带token)

frontend/src/api/axios.js

代码语言:javascript
复制
import axios from 'axios';

const instance = axios.create({ baseURL: 'http://localhost:5000/api' });

instance.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

export default instance;

2. 认证上下文

frontend/src/context/AuthContext.jsx

代码语言:javascript
复制
import React, { createContext, useState, useContext } from 'react';
import api from '../api/axios';

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  const login = async (username, password) => {
    const { data } = await api.post('/auth/login', { username, password });
    localStorage.setItem('token', data.token);
    setUser(data.username);
  };

  const register = async (username, password) => {
    const { data } = await api.post('/auth/register', { username, password });
    localStorage.setItem('token', data.token);
    setUser(data.username);
  };

  const logout = () => {
    localStorage.removeItem('token');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, register, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => useContext(AuthContext);

3. 任务列表组件

frontend/src/components/TaskList.jsx

代码语言:javascript
复制
import React, { useState, useEffect } from 'react';
import api from '../api/axios';

function TaskList() {
  const [tasks, setTasks] = useState([]);
  const [title, setTitle] = useState('');

  useEffect(() => { fetchTasks(); }, []);

  const fetchTasks = async () => {
    const { data } = await api.get('/tasks');
    setTasks(data);
  };

  const addTask = async () => {
    if (!title.trim()) return;
    await api.post('/tasks', { title });
    setTitle('');
    fetchTasks();
  };

  const toggleTask = async (id, completed) => {
    await api.patch(`/tasks/${id}`, { completed: !completed });
    fetchTasks();
  };

  const deleteTask = async (id) => {
    await api.delete(`/tasks/${id}`);
    fetchTasks();
  };

  return (
    <div>
      <div>
        <input value={title} onChange={e => setTitle(e.target.value)} placeholder="新任务" />
        <button onClick={addTask}>添加</button>
      </div>
      <ul>
        {tasks.map(task => (
          <li key={task._id}>
            <span style={{ textDecoration: task.completed ? 'line-through' : 'none' }}
                  onClick={() => toggleTask(task._id, task.completed)}>
              {task.title}
            </span>
            <button onClick={() => deleteTask(task._id)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TaskList;

四、运行与测试

  1. 启动MongoDB(本地或云实例)。
  2. 后端:cd backend && npm install && npm start
  3. 前端:cd frontend && npm install && npm run dev
  4. 注册用户后登录,即可对任务进行增删改查,所有操作通过JWT认证保护。

总结

本文展示了MERN全栈应用的完整实现,涵盖数据库建模、REST API设计、JWT认证、React状态管理和前后端联调。全栈开发的精髓在于打通数据流:前端触发HTTP请求,后端验证身份并操作数据库,返回JSON数据,前端再渲染更新。掌握这一流程后,开发者可以快速构建各种类型的Web应用。建议进一步学习TypeScript、Next.js、GraphQL等现代全栈技术,提升代码健壮性和开发体验。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 全栈开发实战:用MERN构建用户认证与任务管理应用
    • 一、技术栈与项目结构
    • 二、后端实现
      • 1. 数据库模型
      • 2. 认证中间件
      • 3. 路由处理
    • 三、前端实现
      • 1. Axios实例(自动携带token)
      • 2. 认证上下文
      • 3. 任务列表组件
    • 四、运行与测试
    • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档