Build a Tech Blog from Scratch: Spring Boot + Vue3 Full‑Stack Tutorial (Part 1)

This article launches a new series that walks through building a complete technical blog system from the ground up, detailing the chosen Spring Boot and Vue3 stack, phased feature roadmap, database schema, project structure, and initial unified response and global exception handling code.

Coder Trainee
Coder Trainee
Coder Trainee
Build a Tech Blog from Scratch: Spring Boot + Vue3 Full‑Stack Tutorial (Part 1)

Technology Selection

Frontend framework: Vue3 + Composition API – flexible code organization, TypeScript‑friendly.

Build tool: Vite – fast cold start and instant hot updates.

UI component library: Element Plus – mature in China with comprehensive documentation.

State management: Pinia – lightweight official replacement for Vuex.

HTTP client: Axios – widely used.

Backend framework: Spring Boot 2.7.x – extensive ecosystem.

ORM: MyBatis‑Plus – reduces about 80% of CRUD code.

Database: MySQL 8.0 + Redis – conventional combination for persistence and caching.

Security: Spring Security + JWT – standard front‑back separation for authentication.

API documentation: Knife4j (enhanced Swagger) – auto‑generated, convenient for debugging.

File storage: Qiniu Cloud / Alibaba Cloud OSS – store images externally rather than in the database.

Feature Planning

Phase 1 – Basic Version

User registration/login (JWT)

Article list and detail pages

Article categories and tags

Comment system (post & reply)

Admin backend for managing articles and categories

Phase 2 – Advanced Version

Article search powered by Elasticsearch with tokenization

Read‑count statistics using Redis deduplication

Like & favorite functionality

Follow / follower relationships

Personal homepage with activity feed

Friendship links

Phase 3 – High‑End Version

Message notifications via WebSocket and internal messaging

Third‑party login (GitHub / WeChat)

Article review workflow with sensitive‑word filtering

Data statistics (UV/PV, daily active users)

Rich‑text editor supporting Markdown and drag‑and‑drop image upload

Database Design (Core Tables)

User Table user

CREATE TABLE `user` (
  `id` bigint PRIMARY KEY AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '加密密码',
  `nickname` varchar(50) COMMENT '昵称',
  `avatar` varchar(500) COMMENT '头像URL',
  `email` varchar(100) COMMENT '邮箱',
  `role` varchar(20) DEFAULT 'user' COMMENT 'user/admin',
  `status` tinyint DEFAULT 1 COMMENT '1-正常 0-禁用',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY `uk_username` (`username`)
);

Article Table article

CREATE TABLE `article` (
  `id` bigint PRIMARY KEY AUTO_INCREMENT,
  `title` varchar(200) NOT NULL COMMENT '标题',
  `summary` varchar(500) COMMENT '摘要',
  `content` longtext COMMENT 'Markdown内容',
  `cover_image` varchar(500) COMMENT '封面图',
  `category_id` bigint COMMENT '分类ID',
  `user_id` bigint NOT NULL COMMENT '作者ID',
  `view_count` int DEFAULT 0 COMMENT '阅读量',
  `like_count` int DEFAULT 0 COMMENT '点赞数',
  `comment_count` int DEFAULT 0 COMMENT '评论数',
  `status` tinyint DEFAULT 1 COMMENT '1-发布 2-草稿 3-私密',
  `is_top` tinyint DEFAULT 0 COMMENT '0-不置顶 1-置顶',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY `idx_category` (`category_id`),
  KEY `idx_user` (`user_id`),
  KEY `idx_create_time` (`create_time`)
);

Category Table category

CREATE TABLE `category` (
  `id` bigint PRIMARY KEY AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '分类名',
  `sort_order` int DEFAULT 0 COMMENT '排序',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP
);

Article‑Tag Association article_tag and Tag Table tag

CREATE TABLE `article_tag` (
  `id` bigint PRIMARY KEY AUTO_INCREMENT,
  `article_id` bigint NOT NULL,
  `tag_id` bigint NOT NULL,
  UNIQUE KEY `uk_article_tag` (`article_id`, `tag_id`)
);

CREATE TABLE `tag` (
  `id` bigint PRIMARY KEY AUTO_INCREMENT,
  `name` varchar(30) NOT NULL COMMENT '标签名',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY `uk_name` (`name`)
);

Comment Table comment

CREATE TABLE `comment` (
  `id` bigint PRIMARY KEY AUTO_INCREMENT,
  `article_id` bigint NOT NULL,
  `user_id` bigint NOT NULL,
  `parent_id` bigint DEFAULT 0 COMMENT '父评论ID,0表示顶级评论',
  `reply_to_user_id` bigint COMMENT '回复的目标用户ID',
  `content` varchar(1000) NOT NULL,
  `like_count` int DEFAULT 0,
  `status` tinyint DEFAULT 1 COMMENT '1-正常 0-删除',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  KEY `idx_article` (`article_id`),
  KEY `idx_user` (`user_id`)
);

Project Initialization

Backend Structure (Maven Multi‑Module)

blog-backend/
├── blog-common/          # Common utilities and unified response
├── blog-framework/       # Framework config (Security, MyBatis‑Plus)
├── blog-generator/        # MyBatis‑Plus code generator
├── blog-admin/            # Admin API
├── blog-api/              # Front‑end API
└── blog-system/          # System management (users, roles)

Frontend Structure (Vite + Vue3)

blog-frontend/
├── src/
│   ├── api/               # API wrappers
│   ├── assets/            # Static assets
│   ├── components/        # Shared components
│   ├── composables/       # Composition functions
│   ├── layout/            # Layout components
│   ├── router/            # Route definitions
│   ├── stores/            # Pinia stores
│   ├── utils/             # Utility functions
│   ├── views/             # Pages
│   │   ├── home/          # Home page
│   │   ├── article/       # Article detail
│   │   ├── user/          # User center
│   │   └── admin/         # Admin panel
│   └── main.js
├── index.html
└── vite.config.js

Unified Response & Global Exception Handling

Result<T> Class

@Data
public class Result<T> {
    private Integer code;
    private String msg;
    private T data;

    private Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> error(String msg) {
        return new Result<>(500, msg, null);
    }

    public static <T> Result<T> error(Integer code, String msg) {
        return new Result<>(code, msg, null);
    }
}

GlobalExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidException(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(";"));
        return Result.error(400, msg);
    }

    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error("系统繁忙,请稍后重试");
    }
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Redisspring-bootMySQLtutorialJWTVue3full-stack
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

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.