阿里云主机折上折
  • 微信号
Current Site:Index > The implementation of MVC architecture in Koa2

The implementation of MVC architecture in Koa2

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

Overview of MVC Architecture

MVC (Model-View-Controller) is a classic software design pattern that divides an application into three core components: Model, View, and Controller. Implementing the MVC architecture in Koa2 can lead to better code organization and maintainability.

  • Model: Responsible for data processing and business logic
  • View: Responsible for data presentation and user interface
  • Controller: Acts as a middle layer, handling user input and coordinating the Model and View

Basic Structure of Koa2

Before implementing the MVC architecture in Koa2, you need to establish a basic project structure:

project/  
├── app.js          # Application entry file  
├── config/         # Configuration directory  
├── controllers/    # Controllers directory  
├── models/         # Models directory  
├── views/          # Views directory  
├── routes/         # Routes directory  
└── middlewares/    # Middleware directory  

Basic Koa2 application example:

const Koa = require('koa');  
const app = new Koa();  

app.use(async ctx => {  
  ctx.body = 'Hello Koa';  
});  

app.listen(3000);  

Implementation of the Model Layer

The model layer is responsible for interacting with data sources, typically including database operations and business logic. Below is an example of a user model:

// models/user.js  
const db = require('../config/database');  

class User {  
  static async findById(id) {  
    return await db.query('SELECT * FROM users WHERE id = ?', [id]);  
  }  

  static async create(userData) {  
    const result = await db.query(  
      'INSERT INTO users SET ?',   
      userData  
    );  
    return result.insertId;  
  }  

  static async update(id, userData) {  
    await db.query(  
      'UPDATE users SET ? WHERE id = ?',  
      [userData, id]  
    );  
    return true;  
  }  
}  

module.exports = User;  

Implementation of the Controller Layer

The controller handles HTTP requests, calls appropriate model methods, and returns responses. Below is an example of a user controller:

// controllers/userController.js  
const User = require('../models/user');  

class UserController {  
  async show(ctx) {  
    const user = await User.findById(ctx.params.id);  
    if (!user) {  
      ctx.status = 404;  
      return;  
    }  
    await ctx.render('users/show', { user });  
  }  

  async create(ctx) {  
    try {  
      const userId = await User.create(ctx.request.body);  
      ctx.status = 201;  
      ctx.body = { id: userId };  
    } catch (error) {  
      ctx.status = 400;  
      ctx.body = { error: error.message };  
    }  
  }  

  async update(ctx) {  
    await User.update(ctx.params.id, ctx.request.body);  
    ctx.status = 204;  
  }  
}  

module.exports = new UserController();  

Implementation of the View Layer

Koa2 typically uses template engines like EJS, Pug, or Nunjucks for the view layer. Below is an example of configuring Nunjucks:

// config/view.js  
const nunjucks = require('nunjucks');  
const path = require('path');  

module.exports = function(app) {  
  const env = nunjucks.configure(path.join(__dirname, '../views'), {  
    autoescape: true,  
    express: app,  
    watch: true  
  });  

  // Add custom filters  
  env.addFilter('shorten', function(str, count) {  
    return str.slice(0, count || 5);  
  });  

  return env;  
};  

View template example (views/users/show.njk):

{% extends "layouts/base.njk" %}  

{% block content %}  
  <h1>User Profile</h1>  
  <div class="user-profile">  
    <p>ID: {{ user.id }}</p>  
    <p>Name: {{ user.name }}</p>  
    <p>Email: {{ user.email|shorten(10) }}</p>  
  </div>  
{% endblock %}  

Route Configuration

Routes map HTTP requests to corresponding controller methods. Below is an example of route configuration:

// routes/userRoutes.js  
const Router = require('koa-router');  
const userController = require('../controllers/userController');  

const router = new Router({ prefix: '/users' });  

router.get('/:id', userController.show);  
router.post('/', userController.create);  
router.put('/:id', userController.update);  

module.exports = router;  

Register routes in the application entry file:

// app.js  
const Koa = require('koa');  
const userRoutes = require('./routes/userRoutes');  

const app = new Koa();  

app.use(userRoutes.routes());  
app.use(userRoutes.allowedMethods());  

app.listen(3000);  

Middleware Integration

Koa2's middleware mechanism is well-suited for cross-cutting concerns in the MVC architecture. Below are examples of commonly used middleware:

  1. Error-handling middleware:
// middlewares/errorHandler.js  
module.exports = async function(ctx, next) {  
  try {  
    await next();  
  } catch (err) {  
    ctx.status = err.status || 500;  
    ctx.body = {  
      error: {  
        message: err.message,  
        status: ctx.status  
      }  
    };  
    ctx.app.emit('error', err, ctx);  
  }  
};  
  1. Authentication middleware:
// middlewares/auth.js  
const jwt = require('jsonwebtoken');  

module.exports = function() {  
  return async function(ctx, next) {  
    const token = ctx.headers.authorization;  
    if (!token) {  
      ctx.throw(401, 'Authentication required');  
    }  

    try {  
      const decoded = jwt.verify(token, process.env.JWT_SECRET);  
      ctx.state.user = decoded;  
      await next();  
    } catch (err) {  
      ctx.throw(401, 'Invalid token');  
    }  
  };  
};  

Service Layer Extension

For complex applications, you can introduce a service layer between the controller and model to handle more intricate business logic:

// services/userService.js  
const User = require('../models/user');  
const Mailer = require('../utils/mailer');  

class UserService {  
  static async register(userData) {  
    const userId = await User.create(userData);  
    const user = await User.findById(userId);  

    // Send welcome email  
    await Mailer.sendWelcomeEmail(user.email, user.name);  

    // Log registration event  
    await EventLogger.log('user_registered', {  
      userId,  
      ip: userData.ip  
    });  

    return user;  
  }  
}  

module.exports = UserService;  

Testing Strategy

The MVC architecture facilitates unit and integration testing. Below is an example of controller testing:

// test/controllers/userController.test.js  
const request = require('supertest');  
const app = require('../../app');  
const User = require('../../models/user');  

describe('UserController', () => {  
  beforeEach(async () => {  
    await User.truncate();  
  });  

  it('should create a new user', async () => {  
    const response = await request(app)  
      .post('/users')  
      .send({  
        name: 'Test User',  
        email: 'test@example.com'  
      });  

    expect(response.status).toBe(201);  
    expect(response.body.id).toBeDefined();  
  });  
});  

Performance Optimization Considerations

Implement performance optimizations in the MVC architecture:

  1. Database query optimization:
// Before optimization  
const user = await User.findById(id);  
const posts = await Post.findByUserId(id);  

// After optimization  
const [user, posts] = await Promise.all([  
  User.findById(id),  
  Post.findByUserId(id)  
]);  
  1. View caching:
// config/view.js  
const env = nunjucks.configure('views', {  
  autoescape: true,  
  express: app,  
  watch: process.env.NODE_ENV !== 'production',  
  noCache: process.env.NODE_ENV !== 'production'  
});  

Example of a Real-World Project Structure

A complete e-commerce project might have the following structure:

ecommerce/  
├── app.js  
├── config/  
│   ├── database.js  
│   ├── view.js  
│   └── ...  
├── controllers/  
│   ├── productController.js  
│   ├── cartController.js  
│   ├── orderController.js  
│   └── ...  
├── models/  
│   ├── Product.js  
│   ├── Cart.js  
│   ├── Order.js  
│   └── ...  
├── services/  
│   ├── paymentService.js  
│   ├── inventoryService.js  
│   └── ...  
├── views/  
│   ├── products/  
│   ├── carts/  
│   ├── orders/  
│   └── ...  
├── routes/  
│   ├── api/  
│   │   ├── v1/  
│   │   └── v2/  
│   └── web/  
└── middlewares/  
    ├── auth.js  
    ├── errorHandler.js  
    └── ...  

Asynchronous Flow Control

Koa2's asynchronous middleware mechanism pairs perfectly with MVC. Below is an example of handling file uploads:

// controllers/fileController.js  
const fs = require('fs/promises');  
const path = require('path');  
const { v4: uuidv4 } = require('uuid');  

class FileController {  
  async upload(ctx) {  
    const file = ctx.request.files.file;  
    const ext = path.extname(file.name);  
    const filename = `${uuidv4()}${ext}`;  
    const dest = path.join(__dirname, '../uploads', filename);  

    await fs.rename(file.path, dest);  

    ctx.body = {  
      url: `/uploads/${filename}`  
    };  
  }  
}  

module.exports = new FileController();  

Dependency Injection Implementation

To improve testability, dependencies can be injected via constructors:

// controllers/productController.js  
class ProductController {  
  constructor({ productService, logger }) {  
    this.productService = productService;  
    this.logger = logger;  
  }  

  async index(ctx) {  
    try {  
      const products = await this.productService.getAll();  
      ctx.body = products;  
    } catch (error) {  
      this.logger.error(error);  
      ctx.status = 500;  
    }  
  }  
}  

// Usage  
const productService = require('../services/productService');  
const logger = require('../utils/logger');  
const productController = new ProductController({ productService, logger });  

RESTful API Design

Example of a RESTful API controller in the MVC architecture:

// controllers/api/postController.js  
const Post = require('../../models/post');  

class PostController {  
  // GET /posts  
  async index(ctx) {  
    const posts = await Post.findAll();  
    ctx.body = posts;  
  }  

  // POST /posts  
  async create(ctx) {  
    const post = await Post.create(ctx.request.body);  
    ctx.status = 201;  
    ctx.body = post;  
  }  

  // GET /posts/:id  
  async show(ctx) {  
    const post = await Post.findById(ctx.params.id);  
    if (!post) ctx.throw(404);  
    ctx.body = post;  
  }  

  // PUT /posts/:id  
  async update(ctx) {  
    await Post.update(ctx.params.id, ctx.request.body);  
    ctx.status = 204;  
  }  

  // DELETE /posts/:id  
  async destroy(ctx) {  
    await Post.delete(ctx.params.id);  
    ctx.status = 204;  
  }  
}  

module.exports = new PostController();  

Form Validation Handling

Example of form validation in a controller:

// controllers/authController.js  
const { validate } = require('../validators');  
const authSchema = require('../validators/schemas/auth');  

class AuthController {  
  async register(ctx) {  
    // Validate input  
    const { error, value } = validate(ctx.request.body, authSchema.register);  
    if (error) {  
      ctx.status = 422;  
      ctx.body = { errors: error.details };  
      return;  
    }  

    // Process business logic  
    const user = await User.create(value);  
    ctx.status = 201;  
    ctx.body = user;  
  }  
}  

module.exports = new AuthController();  

WebSocket Integration

Example of integrating WebSocket in the MVC architecture:

// controllers/chatController.js  
const WebSocket = require('ws');  

class ChatController {  
  constructor() {  
    this.clients = new Set();  
  }  

  init(server) {  
    this.wss = new WebSocket.Server({ server });  

    this.wss.on('connection', (ws) => {  
      this.clients.add(ws);  

      ws.on('message', (message) => {  
        this.broadcast(message);  
      });  

      ws.on('close', () => {  
        this.clients.delete(ws);  
      });  
    });  
  }  

  broadcast(message) {  
    for (const client of this.clients) {  
      if (client.readyState === WebSocket.OPEN) {  
        client.send(message);  
      }  
    }  
  }  
}  

module.exports = new ChatController();  

Scheduled Task Handling

Example of handling scheduled tasks in the MVC architecture:

// services/cronService.js  
const cron = require('node-cron');  
const Order = require('../models/order');  

class CronService {  
  static init() {  
    // Clean up expired orders daily at midnight  
    cron.schedule('0 0 * * *', async () => {  
      const expiredOrders = await Order.findExpired();  
      for (const order of expiredOrders) {  
        await Order.cancel(order.id);  
        console.log(`Cancelled order ${order.id}`);  
      }  
    });  

    // Backup database hourly  
    cron.schedule('0 * * * *', () => {  
      Database.backup();  
    });  
  }  
}  

module.exports = CronService;  

Configuration Management

Example of centralized configuration management:

// config/index.js  
const dotenv = require('dotenv');  
const path = require('path');  

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

module.exports = {  
  app: {  
    port: process.env.PORT || 3000,  
    env: process.env.NODE_ENV || 'development'  
  },  
  db: {  
    host: process.env.DB_HOST,  
    user: process.env.DB_USER,  
    password: process.env.DB_PASSWORD,  
    database: process.env.DB_NAME  
  },  
  jwt: {  
    secret: process.env.JWT_SECRET,  
    expiresIn: '7d'  
  }  
};  

Logging Strategy

Example of implementing structured logging:

// utils/logger.js  
const pino = require('pino');  
const pretty = require('pino-pretty');  

const stream = pretty({  
  colorize: true,  
  translateTime: 'SYS:yyyy-mm-dd HH:MM:ss',  
  ignore: 'pid,hostname'  
});  

const logger = pino({  
  level: process.env.LOG_LEVEL || 'info',  
  formatters: {  
    level(label) {  
      return { level: label };  
    }  
  }  
}, stream);  

// Request logging middleware  
logger.middleware = async function(ctx, next) {  
  const start = Date.now();  
  await next();  
  const ms = Date.now() - start;  

  logger.info({  
    method: ctx.method,  
    path: ctx.path,  
    status: ctx.status,  
    duration: `${ms}ms`,  
    ip: ctx.ip  
  });  
};  

module.exports = logger;  

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

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.