应用结构 #

一、项目结构概述 #

1.1 为什么需要良好的结构? #

优势 说明
可维护性 代码易于查找和修改
可扩展性 方便添加新功能
可读性 团队成员易于理解
可测试性 便于编写单元测试
协作效率 减少团队沟通成本

1.2 结构设计原则 #

  • 单一职责:每个文件只负责一个功能
  • 模块化:功能按模块划分
  • 分层架构:路由、控制器、服务、模型分离
  • 配置分离:配置与代码分离

二、基础结构 #

2.1 简单项目结构 #

适用于小型项目或学习阶段:

text
my-app/
├── node_modules/
├── public/
│   ├── css/
│   ├── js/
│   └── images/
├── routes/
│   ├── index.js
│   └── users.js
├── views/
│   ├── index.ejs
│   └── layout.ejs
├── app.js
├── package.json
└── .env

2.2 app.js示例 #

javascript
const express = require('express');
const path = require('path');
const app = express();

app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');

app.use('/', indexRouter);
app.use('/users', usersRouter);

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('出错了!');
});

module.exports = app;

三、MVC结构 #

3.1 目录结构 #

text
my-app/
├── config/
│   ├── database.js
│   ├── passport.js
│   └── index.js
├── controllers/
│   ├── authController.js
│   ├── userController.js
│   └── postController.js
├── models/
│   ├── User.js
│   ├── Post.js
│   └── Comment.js
├── routes/
│   ├── index.js
│   ├── authRoutes.js
│   ├── userRoutes.js
│   └── postRoutes.js
├── middlewares/
│   ├── auth.js
│   ├── validate.js
│   └── errorHandler.js
├── services/
│   ├── authService.js
│   ├── userService.js
│   └── emailService.js
├── utils/
│   ├── helpers.js
│   ├── constants.js
│   └── validators.js
├── views/
│   ├── layouts/
│   ├── partials/
│   ├── auth/
│   └── pages/
├── public/
│   ├── css/
│   ├── js/
│   └── images/
├── tests/
│   ├── unit/
│   └── integration/
├── app.js
├── server.js
├── package.json
└── .env

3.2 入口文件 #

server.js:

javascript
const app = require('./app');
const connectDB = require('./config/database');

connectDB();

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
    console.log(`服务器运行在端口 ${PORT}`);
});

app.js:

javascript
const express = require('express');
const path = require('path');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');

const routes = require('./routes');
const errorHandler = require('./middlewares/errorHandler');

const app = express();

app.use(helmet());
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

app.use('/api', routes);

app.use(errorHandler);

module.exports = app;

四、分层架构 #

4.1 路由层(Routes) #

负责定义API端点:

javascript
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const { authenticate, validateUser } = require('../middlewares');

router.get('/', userController.getAllUsers);
router.get('/:id', userController.getUserById);
router.post('/', validateUser, userController.createUser);
router.put('/:id', authenticate, validateUser, userController.updateUser);
router.delete('/:id', authenticate, userController.deleteUser);

module.exports = router;

4.2 控制器层(Controllers) #

负责处理请求和响应:

javascript
const userService = require('../services/userService');

const userController = {
    async getAllUsers(req, res, next) {
        try {
            const users = await userService.findAll();
            res.json(users);
        } catch (error) {
            next(error);
        }
    },

    async getUserById(req, res, next) {
        try {
            const { id } = req.params;
            const user = await userService.findById(id);
            if (!user) {
                return res.status(404).json({ error: '用户不存在' });
            }
            res.json(user);
        } catch (error) {
            next(error);
        }
    },

    async createUser(req, res, next) {
        try {
            const userData = req.body;
            const user = await userService.create(userData);
            res.status(201).json(user);
        } catch (error) {
            next(error);
        }
    },

    async updateUser(req, res, next) {
        try {
            const { id } = req.params;
            const userData = req.body;
            const user = await userService.update(id, userData);
            res.json(user);
        } catch (error) {
            next(error);
        }
    },

    async deleteUser(req, res, next) {
        try {
            const { id } = req.params;
            await userService.delete(id);
            res.status(204).send();
        } catch (error) {
            next(error);
        }
    }
};

module.exports = userController;

4.3 服务层(Services) #

负责业务逻辑:

javascript
const User = require('../models/User');

const userService = {
    async findAll() {
        return await User.find().select('-password');
    },

    async findById(id) {
        return await User.findById(id).select('-password');
    },

    async findByEmail(email) {
        return await User.findOne({ email });
    },

    async create(userData) {
        const user = new User(userData);
        await user.save();
        return user;
    },

    async update(id, userData) {
        const user = await User.findByIdAndUpdate(
            id,
            { $set: userData },
            { new: true, runValidators: true }
        ).select('-password');
        return user;
    },

    async delete(id) {
        await User.findByIdAndDelete(id);
    }
};

module.exports = userService;

4.4 模型层(Models) #

负责数据定义:

javascript
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
    name: {
        type: String,
        required: [true, '请输入用户名'],
        trim: true,
        maxlength: [50, '用户名不能超过50个字符']
    },
    email: {
        type: String,
        required: [true, '请输入邮箱'],
        unique: true,
        lowercase: true,
        match: [/^\S+@\S+\.\S+$/, '请输入有效的邮箱地址']
    },
    password: {
        type: String,
        required: [true, '请输入密码'],
        minlength: [6, '密码至少6个字符'],
        select: false
    },
    role: {
        type: String,
        enum: ['user', 'admin'],
        default: 'user'
    },
    createdAt: {
        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);
});

userSchema.methods.comparePassword = async function(password) {
    return await bcrypt.compare(password, this.password);
};

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

五、中间件组织 #

5.1 认证中间件 #

javascript
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const auth = async (req, res, next) => {
    try {
        const token = req.headers.authorization?.split(' ')[1];
        
        if (!token) {
            return res.status(401).json({ error: '请先登录' });
        }
        
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        const user = await User.findById(decoded.id);
        
        if (!user) {
            return res.status(401).json({ error: '用户不存在' });
        }
        
        req.user = user;
        next();
    } catch (error) {
        res.status(401).json({ error: '无效的token' });
    }
};

const authorize = (...roles) => {
    return (req, res, next) => {
        if (!roles.includes(req.user.role)) {
            return res.status(403).json({ error: '没有权限执行此操作' });
        }
        next();
    };
};

module.exports = { auth, authorize };

5.2 验证中间件 #

javascript
const { validationResult } = require('express-validator');

const validate = (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
    }
    next();
};

const validateUser = [
    body('name').notEmpty().withMessage('用户名不能为空'),
    body('email').isEmail().withMessage('请输入有效的邮箱'),
    body('password').isLength({ min: 6 }).withMessage('密码至少6个字符'),
    validate
];

module.exports = { validate, validateUser };

5.3 错误处理中间件 #

javascript
const errorHandler = (err, req, res, next) => {
    console.error(err.stack);

    let statusCode = err.statusCode || 500;
    let message = err.message || '服务器内部错误';

    if (err.name === 'ValidationError') {
        statusCode = 400;
        message = Object.values(err.errors).map(e => e.message).join(', ');
    }

    if (err.name === 'CastError') {
        statusCode = 400;
        message = '无效的ID格式';
    }

    if (err.code === 11000) {
        statusCode = 400;
        message = '该数据已存在';
    }

    res.status(statusCode).json({
        success: false,
        error: message,
        stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
    });
};

class AppError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.statusCode = statusCode;
        this.isOperational = true;
        Error.captureStackTrace(this, this.constructor);
    }
}

module.exports = errorHandler;
module.exports.AppError = AppError;

六、配置管理 #

6.1 配置文件 #

javascript
const dotenv = require('dotenv');

dotenv.config({ path: `.env.${process.env.NODE_ENV || 'development'}` });

module.exports = {
    port: parseInt(process.env.PORT, 10) || 3000,
    env: process.env.NODE_ENV || 'development',
    jwt: {
        secret: process.env.JWT_SECRET,
        expiresIn: process.env.JWT_EXPIRES_IN || '7d'
    },
    database: {
        url: process.env.DATABASE_URL,
        options: {
            useNewUrlParser: true,
            useUnifiedTopology: true
        }
    },
    cors: {
        origin: process.env.CORS_ORIGIN || '*',
        methods: ['GET', 'POST', 'PUT', 'DELETE']
    }
};

6.2 数据库配置 #

javascript
const mongoose = require('mongoose');
const config = require('./index');

const connectDB = async () => {
    try {
        await mongoose.connect(config.database.url, config.database.options);
        console.log('数据库连接成功');
    } catch (error) {
        console.error('数据库连接失败:', error.message);
        process.exit(1);
    }
};

module.exports = connectDB;

七、工具函数 #

7.1 helpers.js #

javascript
const generateToken = (id) => {
    return jwt.sign({ id }, process.env.JWT_SECRET, {
        expiresIn: process.env.JWT_EXPIRES_IN
    });
};

const filterObj = (obj, ...allowedFields) => {
    const newObj = {};
    Object.keys(obj).forEach(key => {
        if (allowedFields.includes(key)) {
            newObj[key] = obj[key];
        }
    });
    return newObj;
};

const asyncHandler = (fn) => {
    return (req, res, next) => {
        Promise.resolve(fn(req, res, next)).catch(next);
    };
};

module.exports = { generateToken, filterObj, asyncHandler };

7.2 constants.js #

javascript
const USER_ROLES = {
    USER: 'user',
    ADMIN: 'admin'
};

const HTTP_STATUS = {
    OK: 200,
    CREATED: 201,
    NO_CONTENT: 204,
    BAD_REQUEST: 400,
    UNAUTHORIZED: 401,
    FORBIDDEN: 403,
    NOT_FOUND: 404,
    INTERNAL_ERROR: 500
};

const ERROR_MESSAGES = {
    USER_NOT_FOUND: '用户不存在',
    INVALID_CREDENTIALS: '邮箱或密码错误',
    UNAUTHORIZED: '请先登录',
    FORBIDDEN: '没有权限'
};

module.exports = { USER_ROLES, HTTP_STATUS, ERROR_MESSAGES };

八、路由组织 #

8.1 路由索引 #

javascript
const express = require('express');
const router = express.Router();

const authRoutes = require('./authRoutes');
const userRoutes = require('./userRoutes');
const postRoutes = require('./postRoutes');

router.use('/auth', authRoutes);
router.use('/users', userRoutes);
router.use('/posts', postRoutes);

router.get('/health', (req, res) => {
    res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

module.exports = router;

8.2 版本控制 #

javascript
const express = require('express');
const app = express();

const v1Routes = require('./routes/v1');
const v2Routes = require('./routes/v2');

app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);

九、总结 #

应用结构要点:

层级 职责
路由层 定义API端点
控制器层 处理请求响应
服务层 业务逻辑
模型层 数据定义
中间件 请求处理

下一步,让我们深入学习Express的路由系统!

最后更新:2026-03-28