Frontend Development 32 min read

Comprehensive Architecture Guide for a Generic Backend Management System Using Vue and Vite

This article presents a detailed architecture solution for building a generic backend management system with Vue, covering project scaffolding choices, Vite configuration, ESLint/Prettier setup, CSS architecture (ITCSS, BEM, ACSS), JWT authentication, dynamic menu design, RBAC, route registration, component caching, and template generation using Plop.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Comprehensive Architecture Guide for a Generic Backend Management System Using Vue and Vite

General Backend Management System Architecture (Vue)

Project Creation and Scaffold Selection (vite or vue-cli)

vue-cli is based on webpack , offering a powerful ecosystem and high configurability but with complex configuration and slower development experience compared to vite .

vite uses esbuild for dev mode and rollup for bundling, providing fast cold starts and seamless HMR; however its ecosystem is still catching up with webpack .

This article mainly explains how to use vite as the scaffold for development (developers can also use webpack for production builds).

Choosing vite over vue-cli avoids the exponential growth of JavaScript bundle size and long build times as projects scale.

Modern browsers support native ES modules, allowing Vite to serve source modules directly with 304 caching and long‑term Cache‑Control headers.

Microsoft announced that IE11 will be unsupported after June 15, 2022; leveraging the latest browser features is the trend.
$ npm init @vitejs/app
$ cd
$ npm install
$ npm run dev

Basic Setup and Code Style Support (eslint + prettier)

Install VSCode extensions: eslint , prettier , vetur (or volar for Vue 3 setup syntax).

eslint

yarn add --dev eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin

prettier

yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier

.prettierrc.js

module.exports = {
    printWidth: 180, // line length
    tabWidth: 4,
    useTabs: false,
    singleQuote: true,
    semi: false,
    trailingComma: 'none',
    bracketSpacing: true,
    jsxSingleQuote: true,
    endOfLine: 'auto'
}

.eslintrc.js

//.eslintrc.js
module.exports = {
    parser: 'vue-eslint-parser',
    parserOptions: {
        parser: '@typescript-eslint/parser', // Specifies the ESLint parser
        ecmaVersion: 2020, // Allows parsing modern ECMAScript features
        sourceType: 'module', // Allows use of imports
        ecmaFeatures: {
            jsx: true
        }
    },
    extends: [
        'plugin:vue/vue3-recommended',
        'plugin:@typescript-eslint/recommended',
        'prettier',
        'plugin:prettier/recommended'
    ]
}

.settings.json (workspace)

{
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    },
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        "vue",
        "typescript",
        "typescriptreact",
        "json"
    ]
}

Directory Structure Example

├─.vscode            // VSCode config files
├─public             // Static assets (no compilation)
├─src                // Source code
│  ├─apis            // Unified API management
│  │  └─modules      // API modules
│  ├─assets          // Static resources
│  │  └─images
│  ├─components      // Project components (Form, Input, Message, Search, Table, etc.)
│  ├─directives      // Directives (e.g., print)
│  ├─hooks           // Custom hooks
│  ├─layouts         // Layout components (dashboard, fullpage)
│  ├─mock            // Mock API data
│  ├─router          // Routing helpers
│  ├─store           // State management
│  ├─styles          // Styles (CSS architecture later)
│  ├─types           // Type definitions
│  ├─utils           // Utility functions
│  └─views           // Page components (normal, system)
└─template           // Template files

CSS Architecture: ITCSS + BEM + ACSS

Neglecting CSS architecture leads to style pollution, overrides, and maintenance difficulties as projects grow.

ITCSS provides a layered methodology for managing CSS.

ITCSS layers:

Layer

Purpose

Settings

Global variables

Tools

Mixins, functions

Generic

Normalize, reset

Base

Type selectors

Objects

Cosmetic‑free design patterns

Components

UI components

Trumps

Only place for

!important

Combine ITCSS with BEM (Block‑Element‑Modifier) and ACSS (Atomic CSS, similar to Tailwind) for a robust style system.

JWT (JSON Web Token)

JWT provides a cross‑domain authentication solution, enabling single sign‑on without server‑side session storage.

Features

Cross‑language via JSON

Small payload, easy transmission

Signed for integrity

Has expiration

Efficient for clustered SSO

Data Structure

Header.Payload.Signature

Security Considerations

Do not store sensitive data in the payload (client can decode).

Protect the secret key.

Prefer HTTPS.

Implementation

Backend (Koa) example:

const router = require('koa-router')();
const jwt = require('jsonwebtoken');
router.post('/login', async (ctx) => {
  try {
    const { userName, userPwd } = ctx.request.body;
    const res = await User.findOne({ userName, userPwd });
    const data = res._doc;
    const token = jwt.sign({ data }, 'secret', { expiresIn: '1h' });
    if (res) {
      data.token = token;
      ctx.body = data;
    }
  } catch (e) {}
});

Frontend Axios interceptor:

// axios request interceptor, add token to Authorization header
service.interceptors.request.use(request => {
  const token = Cookies.get('token');
  token && (request.headers['Authorization'] = token);
  return request;
}, error => {
  Message.error(error);
});

Koa JWT validation:

const app = new Koa();
const router = require('koa-router')();
const jwt = require('jsonwebtoken');
const koajwt = require('koa-jwt');
app.use(koajwt({ secret: 'secret' }));
app.use(async (ctx, next) => {
  await next().catch(err => {
    if (err.status === 401) {
      ctx.body.msg = 'token authentication failed';
    }
  });
});

Menu Design

Traditional approach stores a static menu tree on the frontend; modern practice fetches menu data from the backend (XML configuration) and registers routes dynamically.

Proposed design: maintain a menu.json on the frontend (what‑you‑see‑is‑what‑you‑get), then generate SQL for the backend via Node scripts.

Structure Design

key

type

description

title

string

Menu title

name

string

Unique identifier for routing and buttons (must be unique per level)

type

string

MODULE, MENU, or BUTTON

path

string

Route path

redirect

string

Redirect path

icon

string

Icon for menu/button

component

string

Component path for menu items

hidden

boolean

Whether the menu is hidden in the sidebar

noCache

boolean

Whether the page should be cached

fullscreen

boolean

Whether the page displays fullscreen

children

array

Sub‑menus or buttons

Note: sibling name values must be unique; they are concatenated with hyphens to form a full identifier.

Example menu.json snippet:

[
    {
        "title": "admin",
        "name": "admin",
        "type": "MODULE",
        "children": [
            {
                "title": "Central Console",
                "path": "/platform",
                "name": "platform",
                "type": "MENU",
                "component": "/platform/index",
                "icon": "mdi:monitor-dashboard"
            },
            {
                "title": "System Settings",
                "name": "system",
                "type": "MENU",
                "path": "/system",
                "icon": "ri:settings-5-line",
                "children": [
                    {
                        "title": "User Management",
                        "name": "user",
                        "type": "MENU",
                        "path": "user",
                        "component": "/system/user"
                    },
                    {
                        "title": "Role Management",
                        "name": "role",
                        "type": "MENU",
                        "path": "role",
                        "component": "/system/role"
                    },
                    {
                        "title": "Resource Management",
                        "name": "resource",
                        "type": "MENU",
                        "path": "resource",
                        "component": "/system/resource"
                    }
                ]
            },
            {
                "title": "Utility Functions",
                "name": "function",
                "type": "MENU",
                "path": "/function",
                "icon": "ri:settings-5-line",
                "children": []
            }
        ]
    }
]

The generated SQL can be created by a Node script ( createMenu.js ) that reads menu.json , computes MD5 IDs, and writes a Liquibase‑compatible SQL file.

Node Script Example (createMenu.js)

// createMenu.js
/**
 * =================MENU CONFIG======================
 * This JavaScript creates SQL for Java
 */
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const execSync = require('child_process').execSync; // sync child process
const resolve = dir => path.join(__dirname, dir);
const moment = require('moment');
const gitName = execSync('git show -s --format=%cn').toString().trim();
const md5 = require('md5');

// GLOBAL CONFIG
const INPUT_PATH = resolve('src/router/menu.json');
const OUTPUT_PATH = resolve('./menu.sql');
const TABLE_NAME = 't_sys_menu';

function createSQL(data, name = '', pid, arr = []) {
  data.forEach(function(v, d) {
    if (v.children && v.children.length) {
      createSQL(v.children, name + '-' + v.name, v.id, arr);
    }
    arr.push({
      id: v.id || md5(v.name),
      created_at: moment().format('YYYY-MM-DD HH:mm:ss'),
      modified_at: moment().format('YYYY-MM-DD HH:mm:ss'),
      created_by: gitName,
      modified_by: gitName,
      version: 1,
      is_delete: false,
      code: (name + '-' + v.name).slice(1),
      name: v.name,
      title: v.title,
      icon: v.icon,
      path: v.path,
      sort: d + 1,
      parent_id: pid,
      type: v.type,
      component: v.component,
      redirect: v.redirect,
      full_screen: v.fullScreen || false,
      hidden: v.hidden || false,
      no_cache: v.noCache || false
    });
  });
  return arr;
}

fs.readFile(INPUT_PATH, 'utf-8', (err, data) => {
  if (err) chalk.red(err);
  const menuList = createSQL(JSON.parse(data));
  const sql = menuList
    .map(sql => {
      let value = '';
      for (const v of Object.values(sql)) {
        value += ',';
        if (v === true) {
          value += 1;
        } else if (v === false) {
          value += 0;
        } else {
          value += v ? `'<${v}>'` : null;
        }
      }
      return 'INSERT INTO `' + TABLE_NAME + '` VALUES (' + value.slice(1) + ')\n';
    })
    .join(';');
  const mySQL =
    'DROP TABLE IF EXISTS `' +
    TABLE_NAME +
    '`;\n' +
    'CREATE TABLE `' +
    TABLE_NAME +
    '`(\n' +
    '`id` varchar(64) NOT NULL,\n' +
    "`created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',\n" +
    "`modified_at` timestamp NULL DEFAULT NULL COMMENT '更新时间',\n" +
    "`created_by` varchar(64) DEFAULT NULL COMMENT '创建人',\n" +
    "`modified_by` varchar(64) DEFAULT NULL COMMENT '更新人',\n" +
    "`version` int(11) DEFAULT NULL COMMENT '版本(乐观锁)',\n" +
    "`is_delete` int(11) DEFAULT NULL COMMENT '逻辑删除',\n" +
    "`code` varchar(150) NOT NULL COMMENT '编码',\n" +
    "`name` varchar(50) DEFAULT NULL COMMENT '名称',\n" +
    "`title` varchar(50) DEFAULT NULL COMMENT '标题',\n" +
    "`icon` varchar(50) DEFAULT NULL COMMENT '图标',\n" +
    "`path` varchar(250) DEFAULT NULL COMMENT '路径',\n" +
    "`sort` int(11) DEFAULT NULL COMMENT '排序',\n" +
    "`parent_id` varchar(64) DEFAULT NULL COMMENT '父id',\n" +
    "`type` char(10) DEFAULT NULL COMMENT '类型',\n" +
    "`component` varchar(250) DEFAULT NULL COMMENT '组件路径',\n" +
    "`redirect` varchar(250) DEFAULT NULL COMMENT '重定向路径',\n" +
    "`full_screen` int(11) DEFAULT NULL COMMENT '全屏',\n" +
    "`hidden` int(11) DEFAULT NULL COMMENT '隐藏',\n" +
    "`no_cache` int(11) DEFAULT NULL COMMENT '缓存',\n" +
    'PRIMARY KEY (`id`),\n' +
    'UNIQUE KEY `code` (`code`) USING BTREE\n' +
    ') ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=\'资源\';\n' +
    sql;
  fs.writeFile(OUTPUT_PATH, mySQL, err => {
    if (err) return chalk.red(err);
    console.log(chalk.cyanBright(`SQL file created at ${OUTPUT_PATH}`));
  });
});

RBAC (Role‑Based Access Control)

RBAC links permissions to roles; users inherit permissions by belonging to roles, simplifying permission management.

Page Cache Control

Using Vue's keep-alive with the include option allows selective caching of pages based on their component name , improving user experience while preventing memory bloat.

Note: the include list stores component names, not route names.

Component Packaging and UI Library Wrappers

Encapsulate reusable components with Jest unit tests and Storybook documentation for large teams.

Template Generation with Plop

Use Plop and Inquirer‑directory to generate API and page templates, reducing repetitive boilerplate.

// plopfile.js
const promptDirectory = require('inquirer-directory');
const pageGenerator = require('./template/page/prompt');
const apisGenerator = require('./template/apis/prompt');
module.exports = function (plop) {
  plop.setPrompt('directory', promptDirectory);
  plop.setGenerator('page', pageGenerator);
  plop.setGenerator('apis', apisGenerator);
};

Running Plop guides the user through selecting a target directory, entering an API name, and choosing CRUD operations to generate a ready‑to‑use TypeScript file.

Project Repository

GitHub: https://github.com/sky124380729/levi-vue-admin

Frontend DevelopmentVueJWTViteDynamic RoutingRBACCSS Architecture
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.