Node.js+Express MVC项目整合登录注册与CRUD功能实现问询
Hey Richard, totally get where you're coming from—combining authentication (login/register) and CRUD operations in a single MVC project can feel overwhelming when most tutorials split them into separate apps. Let's build a concrete, clean example together that lets you toggle which feature runs when you start the server.
We'll use Node.js + Express, MongoDB (with Mongoose for ODM), and stick strictly to MVC principles. Here's how it all comes together:
Project Structure
First, let's lay out a scalable folder structure that keeps concerns separated:
your-mvc-project/ ├── config/ │ ├── db.js # Database connection setup │ └── .env # Optional: Store secrets like JWT key, DB URI ├── controllers/ │ ├── authController.js # Login/register business logic │ └── crudController.js # CRUD logic for a sample model (e.g., Product) ├── models/ │ ├── User.js # User schema for authentication │ └── Product.js # Sample schema for CRUD operations ├── routes/ │ ├── authRoutes.js # Auth API endpoints │ └── crudRoutes.js # CRUD API endpoints ├── app.js # Core Express app setup ├── server.js # Entry point to toggle features └── package.json
Core File Examples
Let's fill in each key file with working code:
1. Database Connection (config/db.js)
This handles connecting to MongoDB, shared by both auth and CRUD features:
const mongoose = require('mongoose'); const connectDB = async () => { try { await mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/mvc-demo', { useNewUrlParser: true, useUnifiedTopology: true, }); console.log('✅ MongoDB connected successfully'); } catch (err) { console.error('❌ MongoDB connection failed:', err); process.exit(1); } }; module.exports = connectDB;
2. Models
User Model (models/User.js)
Handles user data and password hashing for auth:
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 }, }); // Hash password before saving to DB userSchema.pre('save', async function (next) { if (!this.isModified('password')) return next(); const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); }); // Compare password for login userSchema.methods.matchPassword = async function (enteredPassword) { return await bcrypt.compare(enteredPassword, this.password); }; module.exports = mongoose.model('User', userSchema);
Product Model (models/Product.js)
Sample model for CRUD operations:
const mongoose = require('mongoose'); const productSchema = new mongoose.Schema({ name: { type: String, required: true }, description: { type: String, required: true }, price: { type: Number, required: true, min: 0 }, }); module.exports = mongoose.model('Product', productSchema);
3. Controllers
Auth Controller (controllers/authController.js)
Handles register and login logic:
const User = require('../models/User'); const jwt = require('jsonwebtoken'); // Register new user exports.registerUser = async (req, res) => { const { username, email, password } = req.body; try { const existingUser = await User.findOne({ email }); if (existingUser) return res.status(400).json({ message: 'User already exists' }); const user = await User.create({ username, email, password }); const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET || 'dev-secret-key', { expiresIn: '30d' }); res.status(201).json({ _id: user._id, username: user.username, email: user.email, token, }); } catch (err) { res.status(500).json({ message: 'Server error', error: err.message }); } }; // Login user exports.loginUser = async (req, res) => { const { email, password } = req.body; try { const user = await User.findOne({ email }); if (!user) return res.status(400).json({ message: 'Invalid credentials' }); const isPasswordMatch = await user.matchPassword(password); if (!isPasswordMatch) return res.status(400).json({ message: 'Invalid credentials' }); const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET || 'dev-secret-key', { expiresIn: '30d' }); res.json({ _id: user._id, username: user.username, email: user.email, token, }); } catch (err) { res.status(500).json({ message: 'Server error', error: err.message }); } };
CRUD Controller (controllers/crudController.js)
Handles CRUD operations for products:
const Product = require('../models/Product'); // Get all products exports.getProducts = async (req, res) => { try { const products = await Product.find(); res.json(products); } catch (err) { res.status(500).json({ message: 'Server error', error: err.message }); } }; // Create new product exports.createProduct = async (req, res) => { const { name, description, price } = req.body; try { const product = await Product.create({ name, description, price }); res.status(201).json(product); } catch (err) { res.status(400).json({ message: 'Invalid data', error: err.message }); } }; // Update product exports.updateProduct = async (req, res) => { try { const product = await Product.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true }); if (!product) return res.status(404).json({ message: 'Product not found' }); res.json(product); } catch (err) { res.status(400).json({ message: 'Invalid data', error: err.message }); } }; // Delete product exports.deleteProduct = async (req, res) => { try { const product = await Product.findByIdAndDelete(req.params.id); if (!product) return res.status(404).json({ message: 'Product not found' }); res.json({ message: 'Product deleted successfully' }); } catch (err) { res.status(500).json({ message: 'Server error', error: err.message }); } };
4. Routes
Auth Routes (routes/authRoutes.js)
Maps endpoints to auth controller functions:
const express = require('express'); const router = express.Router(); const { registerUser, loginUser } = require('../controllers/authController'); router.post('/register', registerUser); router.post('/login', loginUser); module.exports = router;
CRUD Routes (routes/crudRoutes.js)
Maps endpoints to CRUD controller functions:
const express = require('express'); const router = express.Router(); const { getProducts, createProduct, updateProduct, deleteProduct } = require('../controllers/crudController'); router.get('/products', getProducts); router.post('/products', createProduct); router.put('/products/:id', updateProduct); router.delete('/products/:id', deleteProduct); module.exports = router;
5. Core App & Feature Toggle
app.js (Core Express Setup)
We keep the core app separate so we can attach routes conditionally:
const express = require('express'); const app = express(); const connectDB = require('./config/db'); // Middleware app.use(express.json()); // Parse JSON requests // Connect to database connectDB(); // Export app for use in server.js module.exports = app;
server.js (Feature Toggle Logic)
This is where we choose which feature(s) to run:
const app = require('./app'); const authRoutes = require('./routes/authRoutes'); const crudRoutes = require('./routes/crudRoutes'); // Get feature flag from command line argument const selectedFeature = process.argv[2]; // Attach routes based on selected feature if (selectedFeature === 'auth') { app.use('/api/auth', authRoutes); console.log('🔐 Running Authentication feature only'); } else if (selectedFeature === 'crud') { app.use('/api/crud', crudRoutes); console.log('📦 Running CRUD feature only'); } else { // Default: run both features app.use('/api/auth', authRoutes); app.use('/api/crud', crudRoutes); console.log('🔐 + 📦 Running both Authentication and CRUD features'); } // Start server const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`🚀 Server running on http://localhost:${PORT}`); });
6. Package.json Scripts
Add these scripts to easily toggle features:
{ "name": "mvc-auth-crud", "version": "1.0.0", "main": "server.js", "scripts": { "start": "node server.js", "start:auth": "node server.js auth", "start:crud": "node server.js crud" }, "dependencies": { "bcryptjs": "^2.4.3", "express": "^4.18.2", "jsonwebtoken": "^9.0.2", "mongoose": "^7.5.0" } }
How to Use
- Install dependencies:
npm install - Make sure MongoDB is running locally (or update the connection string in
config/db.js) - Run the app with your desired feature:
- Auth only:
npm run start:auth→ Test endpoints likePOST /api/auth/register - CRUD only:
npm run start:crud→ Test endpoints likeGET /api/crud/products - Both features:
npm start→ All endpoints are available
- Auth only:
Key MVC Principles Upheld
- Separation of Concerns: Models handle data schemas, controllers handle business logic, routes handle endpoint mapping—no mixing of responsibilities.
- Reusability: The database connection and core app setup are shared, so you don't duplicate code.
- Scalability: Adding a new feature later just requires adding a model, controller, routes file, and updating the toggle logic in
server.js.
内容的提问来源于stack exchange,提问作者richard

