This project is based on egg.js. The reason is that I made a blog application using Express and KOA, and I just want to try egg.js…

The server side

First, use scaffolding to generate the project NPM init egg –type=simple directly, and then configure the required plugins in the plugin.js file in config. These are required here

  cors: {
    enable: true.package: 'egg-cors',},mongoose: {
    enable: true.package: 'egg-mongoose',},redis: {
    enable: true.package: 'egg-redis',},validate: {
    enable: true.package: 'egg-validate',},io: {
    enable: true.package: 'egg-socket.io',},Copy the code

You also need to configure each plug-in

const userConfig = {
  cors: {
    origin: ['http://127.0.0.1:3000'].credentials: true.allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',},redis: {
    client: {
      port: 6379.host: '127.0.0.1'.password: ' '.db: 0,}},mongoose: {
    client: {
      url: 'mongodb://127.0.0.1/chat'.options: {}, // If you want to add this, do not add startup error?}},io: {
    namespace: {
      '/': {
        connectionMiddleware: ['auth'].packetMiddleware: ['filter'],},},},};Copy the code

Because the server and client are both local, the port is different, so the cross-domain request, here uses the CORS plug-in to handle the cross-domain request, can add a white list in the configuration, in fact, the plug-in is to add access-Control-Allow-Origin header to the response header. Redis and MongoDB need to be installed themselves, and the default configuration is used here.

Egg.js uses a good directory structure, and we need to pay attention to the following:

  1. The router is used to provide the interface to the front end. Of course, the plug-in allows you to configure the route of the socket to handle socket events

  2. Controller A controller that handles logic and returns a response

  3. Service generally encapsulates business logic, avoids excessive controller, and facilitates reuse

  4. Model defines the data structure

router

module.exports = app= > {
  const { router, controller, io, middleware } = app;
  const auth = middleware.auth();

  router.get('/', controller.home.index);
  router.post('/join', controller.home.join);
  router.get('/list', auth, controller.home.userList);
  router.get('/userInfo', auth, controller.home.userInfo);
  io.of('/').route('message', io.controller.chat.message);
  io.of('/').route('join', io.controller.chat.join);
};
Copy the code

The router is defined with the path and controller. You can also bring other middleware, such as auth, which is used to process user login. The egg is based on directory structure, and middleware can be called directly when placed in a directory

model

Model here is relatively simple, just a user information, inside a user name and user ID, use Mongoose to create a UserModel

module.exports = app= > {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;
  const UserSchema = new Schema({
    name: {
      type: String.required: true,},__v: { type: Number.select: false}});return mongoose.model('User', UserSchema);
};
Copy the code

service

In this case, the Service layer is simply used to retrieve user information

class UserService extends Service {
  async getUserByName(name) {
    return await this.ctx.model.User.findOne({ name });
  }
  async createUser(name) {
    return await this.ctx.model.User.create({ name });
  }
  async getUsers() {
    return await this.ctx.model.User.find(); }}Copy the code

Some methods of Mongoose are used to centralize user-related operations and facilitate multiple calls

Controller

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = 'hi, egg';
  }
  async userInfo() {
    const user = this.ctx.session.user;
    if (user) {
      this.ctx.body = user;
    } else {
      this.ctx.status = 401; }}async join() {
    const { ctx } = this;
    const name = ctx.request.body.name;
    this.ctx.validate({
      name: {
        type: 'string'.trim: true.max: 8.min: 1.format: /^[\u4e00-\u9fa5a-zA-Z0-9]+$/,}});const user = await ctx.service.user.getUserByName(name);
    if (user) {
      ctx.status = 403;
      ctx.body = {
        msg: 'User already exists'};return;
    }
    const newUser = await ctx.service.user.createUser(name);
    console.log('join', newUser);
    ctx.session.user = newUser;
    ctx.body = newUser;
  }
  async userList() {
    const { ctx } = this;
    const list = await ctx.service.user.getUsers();
    list.push({
      name: 'All Users'._id: 'groupall'}); ctx.body = list; }}Copy the code

The controller is the key point of the whole program. Compared with the route, the route processing methods are provided here respectively. Ctx. body or ctx.status can be used to process the return. If the user name exists, 403 will be returned to tell the user that it exists. If not, session will be written to the database. Egg.js has integrated session function, and EGG_SESS will be used to process cookies. So here we’re encrypting the session object and storing it in a cookie.

class ChatController extends Controller {
  async index() {
    this.ctx.socket.emit('msg'.'hello');
  }
  async join() {
    const { ctx } = this;
    const id = ctx.socket.id;
    console.log('socket-id:', id);
    ctx.socket.join('all');
    console.log(ctx.session.user);
    if (ctx.session.user) {
      const userId = ctx.session.user._id;
      awaitctx.app.redis.set(userId, id); }}async message() {
    const message = this.ctx.args[0];
    const { userId, msg } = message;
    const user = this.ctx.session.user;
    if (userId === 'groupall') {
      this.ctx.socket.to('all').emit('msg', { ...user, msg });
      return;
    }
    const socketId = await this.ctx.app.redis.get(userId);
    console.dir({ ... message, socketId });this.ctx.socket.to(socketId).emit('msg', {... user, msg }); }}Copy the code

Socket events are handled in the same way as common HTTP routes. In this case, the CTX object has the socket attribute. After the client establishes a socket connection, it will have a socket ID and send messages to the corresponding client through this ID. SocketIO ctx.socket.join(‘all’) adds all users who have established a connection to the all room. This allows group chat. The message method handles communication between clients and servers, sending messages to everyone in the room in a group chat, or to a single person otherwise.

The server side is finished. Although very simple, it still integrates many functions and can be extended on this basis.

The client side

The React client is not important, so the interface is relatively simple, focusing on the implementation of functions

Since eggJS is enabled to prevent CSRF attacks, it writes a csrfToken field to the cookie. This value needs to be read every time the interface is accessed and added to the header.

import axios from 'axios';
import Cookies from 'js-cookie';
const instance = axios.create({
  baseURL: 'http://127.0.0.1:7002/'.withCredentials: true}); instance.interceptors.request.use(function(config) {
  const csrfToken = Cookies.get('csrfToken');
  config.headers['x-csrf-token'] = csrfToken;
  return config;
});
export default instance;
Copy the code
const initSocket = (a)= > {
  const socket = io('http://127.0.0.1:7002/');
  socket.on('connect', () = > {const id = socket.id;
    socket.on('msg', handleMsg);
    socket.emit('join');
  });
  socketRef.current = socket;
};
Copy the code

This is to establish a socket connection, listen for MSG events to receive the message returned by the server.

Although the whole example is not complicated, the sparrow has all the five organs, and other functions can be further expanded on this basis. You can look at the full server side code, the Web side code, if you want.