You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

  1. Install dependencies: npm install
  2. Make sure MongoDB is running locally (or update the connection string in config/db.js)
  3. Run the app with your desired feature:
    • Auth only: npm run start:auth → Test endpoints like POST /api/auth/register
    • CRUD only: npm run start:crud → Test endpoints like GET /api/crud/products
    • Both features: npm start → All endpoints are available

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 23:03:13