Koa Project Architecture
Directory Structure Design
A typical Koa2 project directory structure is as follows:
project/
├── app.js
├── config/
│ └── index.js
├── controllers/
│ ├── index.js
│ └── userController.js
├── middleware/
│ ├── errorMiddleware.js
│ └── loggerMiddleware.js
├── models/
│ ├── index.js
│ └── userModel.js
├── routes/
│ ├── index.js
│ └── userRoutes.js
├── services/
│ ├── index.js
│ └── userService.js
└── utils/
└── responseUtil.jsapp.js: Main entry file, initializes the Koa application.config/: Configuration files, such as database settings and environment variables.controllers/: Controller layer, handles business logic and route responses.middleware/: Middleware, processes logic before and after requests, such as error handling and logging.models/: Data models, interacts with the database.routes/: Route definitions.services/: Service layer, encapsulates business logic.utils/: Utility functions, such as response formatting.
Initializing the Koa Application
Initialize the Koa application in the app.js file:
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const cors = require('@koa/cors');
const router = require('./routes');
const app = new Koa();
// Use middleware
app.use(bodyParser());
app.use(cors());
// Register routes
app.use(router.routes()).use(router.allowedMethods());
// Start the application
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});Configuration Management
Manage configuration information in the config/index.js file:
module.exports = {
database: {
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb'
},
port: 3000
};Database Integration
Use Sequelize as the ORM in models/userModel.js:
const { Model, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
class User extends Model {}
User.init({
name: DataTypes.STRING,
email: DataTypes.STRING,
}, { sequelize, modelName: 'user' });
module.exports = User;Routes and Controllers
Define routes and controllers in routes/userRoutes.js and controllers/userController.js:
// userRoutes.js
const Router = require('koa-router');
const UserController = require('../controllers/userController');
const router = new Router();
router.get('/', UserController.getAllUsers);
router.post('/', UserController.createUser);
module.exports = router;
// userController.js
const UserService = require('../services/userService');
const getAllUsers = async (ctx) => {
const users = await UserService.getAllUsers();
ctx.body = users;
};
const createUser = async (ctx) => {
const newUser = await UserService.createUser(ctx.request.body);
ctx.body = newUser;
};
module.exports = {
getAllUsers,
createUser
};Service Layer
Encapsulate business logic in services/userService.js:
const User = require('../models/userModel');
const getAllUsers = async () => {
return await User.findAll();
};
const createUser = async (data) => {
return await User.create(data);
};
module.exports = {
getAllUsers,
createUser
};Middleware
Implement error handling and logging in middleware/errorMiddleware.js and middleware/loggerMiddleware.js:
// errorMiddleware.js
module.exports = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
message: err.message
};
}
};
// loggerMiddleware.js
module.exports = async (ctx, next) => {
console.log(`${ctx.method} ${ctx.url}`);
await next();
};Utility Functions
Define response formatting functions in utils/responseUtil.js:
module.exports = {
successResponse: (data) => ({
status: 'success',
data
}),
errorResponse: (message) => ({
status: 'error',
message
})
};Environment Variable Management
Use environment variables to manage sensitive information and configurations, avoiding hardcoding to enhance security and flexibility. Libraries like dotenv or cross-env can be used to load and set environment variables.
# .env file
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=password
DB_NAME=testdb
PORT=3000Read environment variables in config/index.js:
require('dotenv').config();
module.exports = {
database: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
},
port: process.env.PORT
};Unit Testing
Writing unit tests ensures code quality and stability. Testing frameworks like Mocha and assertion libraries like Chai can be used for testing.
// test/userService.test.js
const chai = require('chai');
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
chai.use(sinonChai);
const UserService = require('../../services/userService');
const User = require('../../models/userModel');
describe('UserService', () => {
let findStub;
let createStub;
beforeEach(() => {
findStub = sinon.stub(User, 'findAll');
createStub = sinon.stub(User, 'create');
});
afterEach(() => {
findStub.restore();
createStub.restore();
});
describe('getAllUsers', () => {
it('should return all users', async () => {
findStub.resolves([{ name: 'John Doe' }]);
const users = await UserService.getAllUsers();
expect(users).to.be.an('array');
expect(users[0].name).to.equal('John Doe');
});
});
describe('createUser', () => {
it('should create a new user', async () => {
createStub.resolves({ name: 'Jane Doe' });
const newUser = await UserService.createUser({ name: 'Jane Doe' });
expect(newUser.name).to.equal('Jane Doe');
});
});
});



