全栈开发要求开发者同时掌握前端、后端和数据库。MERN(MongoDB、Express、React、Node.js)是经典的JavaScript全栈组合,前后端统一语言,开发效率高。本文通过一个带JWT认证的任务管理应用,展示全栈开发的核心流程。
项目结构:
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.jsonbackend/models/User.js:
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:
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);backend/middleware/auth.js:
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' });
}
};backend/routes/auth.js:
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):
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:
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));frontend/src/api/axios.js:
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;frontend/src/context/AuthContext.jsx:
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);frontend/src/components/TaskList.jsx:
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;cd backend && npm install && npm start。cd frontend && npm install && npm run dev。本文展示了MERN全栈应用的完整实现,涵盖数据库建模、REST API设计、JWT认证、React状态管理和前后端联调。全栈开发的精髓在于打通数据流:前端触发HTTP请求,后端验证身份并操作数据库,返回JSON数据,前端再渲染更新。掌握这一流程后,开发者可以快速构建各种类型的Web应用。建议进一步学习TypeScript、Next.js、GraphQL等现代全栈技术,提升代码健壮性和开发体验。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。