阿里云主机折上折
  • 微信号
Current Site:Index > Reasonable planning of the project directory structure

Reasonable planning of the project directory structure

Author:Chuan Chen 阅读数:64522人阅读 分类: Node.js

A reasonable project directory structure is the foundation for the maintainability and scalability of a Koa2 application. Clear module division can significantly improve team collaboration efficiency and reduce long-term maintenance costs. Below is a detailed explanation, from core directory design to specific implementation details.

Core Directory Structure Design

A typical Koa2 project should adopt a hybrid structure that prioritizes functional module division while incorporating technical layering. The basic directory should include the following core components:

project-root/
├── src/
│   ├── app.js          # Application entry
│   ├── config/         # Configuration files
│   ├── controllers/    # Controller layer
│   ├── services/       # Business logic layer
│   ├── models/         # Data model layer
│   ├── routes/         # Route definitions
│   ├── middlewares/    # Custom middleware
│   ├── utils/          # Utility functions
│   └── public/         # Static resources
├── tests/              # Test code
├── .env                # Environment variables
└── package.json

Configuration Management Standards

Configuration files should be separated by environment and loaded with sensitive information via dotenv. The following structure is recommended:

// config/default.js
module.exports = {
  port: 3000,
  db: {
    host: 'localhost',
    name: 'dev_db'
  }
};

// config/production.js
const { merge } = require('lodash');
const baseConfig = require('./default');

module.exports = merge(baseConfig, {
  port: process.env.APP_PORT,
  db: {
    host: process.env.DB_HOST,
    name: process.env.DB_NAME
  }
});

Dynamically load configurations during application startup:

// app.js
const environment = process.env.NODE_ENV || 'development';
const config = require(`./config/${environment}`);

Best Practices for Route Organization

Avoid cramming all routes into a single file; split them by business module:

// routes/api/v1/users.js
const router = require('koa-router')();
const UserController = require('../../controllers/user');

router.prefix('/api/v1/users');

router.get('/', UserController.list);
router.post('/:id', UserController.create);

module.exports = router;

Aggregate routes in the main routing file:

// routes/index.js
const combineRouters = require('koa-combine-routers');
const userRouter = require('./api/v1/users');
const productRouter = require('./api/v1/products');

module.exports = combineRouters(
  userRouter,
  productRouter
);

Layered Middleware Management

Custom middleware should be stored independently and categorized by functionality:

middlewares/
├── error-handler.js    # Error handling
├── request-logger.js   # Request logging
└── auth/              # Authentication-related
    ├── jwt.js
    └── basic-auth.js

Example authentication middleware implementation:

// middlewares/auth/jwt.js
const jwt = require('jsonwebtoken');

module.exports = (opts = {}) => {
  return async (ctx, next) => {
    const token = ctx.headers.authorization?.split(' ')[1];
    try {
      ctx.state.user = jwt.verify(token, opts.secret);
      await next();
    } catch (err) {
      ctx.throw(401, 'Invalid token');
    }
  };
};

Business Logic Layering

In a typical MVC architecture, the responsibilities of each layer should be clearly defined:

// controllers/user.js
const UserService = require('../services/user');

class UserController {
  static async list(ctx) {
    const users = await UserService.findAll();
    ctx.body = { data: users };
  }
}

// services/user.js
const UserModel = require('../models/user');

class UserService {
  static async findAll() {
    return UserModel.find().lean();
  }
}

// models/user.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: String,
  email: String
});

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

Static Resource Handling

Static files should be separated from code and handled by dedicated middleware:

// app.js
const serve = require('koa-static');
const mount = require('koa-mount');

app.use(mount('/assets', serve('public/assets')));
app.use(mount('/uploads', serve('storage/uploads')));

Recommended directory structure:

public/
├── assets/
│   ├── css/
│   ├── js/
│   └── images/
└── favicon.ico
storage/
└── uploads/
    ├── avatars/
    └── documents/

Test Code Organization

The test directory should mirror the source code structure and include test type suffixes:

tests/
├── unit/
│   ├── services/
│   └── utils/
├── integration/
│   ├── api/
│   └── middlewares/
└── fixtures/       # Test fixtures

Example test file:

// tests/unit/services/user.test.js
const UserService = require('../../../src/services/user');
const mockUserModel = {
  find: jest.fn().mockResolvedValue([{name: 'test'}])
};

jest.mock('../../../src/models/user', () => mockUserModel);

describe('UserService', () => {
  it('should return user list', async () => {
    const users = await UserService.findAll();
    expect(users).toHaveLength(1);
  });
});

Environment-Specific File Handling

Files specific to different environments should be distinguished by convention:

.env.development
.env.test
.env.production

Configure startup commands in package.json:

{
  "scripts": {
    "dev": "NODE_ENV=development nodemon src/app.js",
    "test": "NODE_ENV=test jest",
    "start": "NODE_ENV=production node src/app.js"
  }
}

Utility Function Management

General utility functions should be categorized by functionality:

utils/
├── date-utils.js     # Date handling
├── crypto-utils.js   # Encryption-related
├── validation.js     # Data validation
└── api/              # API-related
    ├── wrapper.js    # Response wrapping
    └── error.js      # API errors

Example response wrapper:

// utils/api/wrapper.js
module.exports = {
  success(data, meta = {}) {
    return {
      status: 'success',
      data,
      meta
    };
  },
  error(message, code = 400) {
    return {
      status: 'error',
      message,
      code
    };
  }
};

Log File Management

Production environment logs should be split by type and date:

logs/
├── access/
│   ├── 2023-08-01.log
│   └── 2023-08-02.log
├── error/
│   └── 2023-08-01.error.log
└── application/
    └── 2023-08-01.app.log

Example logging middleware configuration:

// middlewares/logger.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const appendFile = promisify(fs.appendFile);

module.exports = (options = {}) => {
  return async (ctx, next) => {
    const start = Date.now();
    await next();
    const log = `${new Date().toISOString()} | ${ctx.method} ${ctx.url} | ${ctx.status} | ${Date.now() - start}ms\n`;
    await appendFile(path.join(options.logDir, `${new Date().toISOString().split('T')[0]}.log`), log);
  };
};

Documentation and Script Management

Project auxiliary files should be stored uniformly:

docs/
├── api.md           # API documentation
├── db-schema.md     # Database design
└── setup-guide.md   # Environment setup guide
scripts/
├── db-migrate.js    # Database migration
└── seed-data.js     # Initial data

Third-Party Module Extensions

When integrating third-party modules, create dedicated directories:

lib/
├── email/           # Email service encapsulation
│   ├── sender.js
│   └── templates/
├── payment/         # Payment gateway
│   └── stripe.js
└── sms/             # SMS service
    └── twilio.js

Example email service encapsulation:

// lib/email/sender.js
const nodemailer = require('nodemailer');

class EmailSender {
  constructor(config) {
    this.transporter = nodemailer.createTransport(config);
  }

  async send(templateName, to, data) {
    const template = require(`./templates/${templateName}`);
    return this.transporter.sendMail({
      to,
      html: template(data)
    });
  }
}

module.exports = EmailSender;

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.