Reasonable planning of the project directory structure
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
上一篇:常用开发工具与调试技巧