Backend Development 22 min read

Design and Implementation of the Service Backend Architecture for Editor, Component Platform, and C‑End Application

This article details the overall backend architecture, module relationships, RESTful API design, database schemas, authentication flow, activity creation process, JSON schema validation, component platform APIs, and server‑side rendering considerations for a Node.js‑based service supporting editors, components, and end‑user pages.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Design and Implementation of the Service Backend Architecture for Editor, Component Platform, and C‑End Application

This article is the first signed piece for the Rare Earth Juejin technical community and may not be reproduced within 14 days without permission.

In the previous article I explained the rationale behind our Node.js framework, database, authentication, and testing choices; this article dives into the overall server‑side architecture and the relationships between its modules.

The overall architecture diagram from the first column is reused here to illustrate the system.

Editor Backend Design

The editor module is the core operation area, handling user registration/login, activity creation/modification/preview/publishing, and media uploads.

It is divided into three parts: User Information, Activity Management, and Utility Tools.

The code follows a standard Restful API style: a route receives the request, a middleware validates the parameters, a controller dispatches the call, and a service interacts with the database via an ORM framework.

User‑related endpoints

POST /users/login (shared with registration)

GET /users/getUserInfo

PATCH /users/updateUserInfo

Activity‑related endpoints

POST /activity/

POST /activity/copy/:id

GET /activity/:id

PATCH /activity/:id

DELETE /activity/:id

POST /activity/publish/:id

GET /activity/list

Utility endpoints

POST /utils/uploadImg

POST /utils/uploadVideo

Below are the database table designs.

Data Table Design

Designing tables early is crucial for large projects to define field types, meanings, and relationships.

We use MySQL (relational) for user and activity metadata and MongoDB (document) for activity content JSON.

User Table

The user table stores username, password, phone number, gender, and avatar.

Column

Type

Comment

username

varchar

Unique username

password

varchar

Password

phoneNumber

varchar

Phone number

gender

int

1 male, 2 female, 0 secret

avatar

varchar

User avatar URL

const seq = require("../db/seq/seq");
const { STRING, DATE, BOOLEAN } = require("../db/seq/types");
const User = seq.define("user", {
  username: { type: STRING, allowNull: false, unique: "username", comment: "用户名" },
  password: { type: STRING, allowNull: false, comment: "密码" },
  phoneNumber: { type: STRING, allowNull: false, unique: "phoneNumber", comment: "手机号" },
  gender: { type: STRING, allowNull: false, defaultValue: 0, comment: "性别(1 男性,2 女性,0 保密)" },
  avatar: { type: STRING, comment: "头像(图片地址)" }
});
module.exports = User;

Activity Table

The activity table stores title, description, a reference to the MongoDB content document, author, cover image, status, and the latest publish timestamp.

Column

Type

Comment

title

varchar

Title

desc

varchar

Description

contentId

varchar

Reference to MongoDB content

author

varchar

Author username (foreign key to user)

coverImg

varchar

Cover image URL

status

int

0 deleted, 1 unpublished, 2 published, 3 forced offline

latestPublishAt

date

Last publish time

const seq = require("../db/seq/seq");
const { STRING, DATE, BOOLEAN, INTEGER } = require("../db/seq/types");
const UserModel = require("./UserModel");
const Activity = seq.define("activity", {
  uuid: { type: STRING, allowNull: false, unique: "uuid", comment: "uuid" },
  title: { type: STRING, allowNull: false, comment: "标题" },
  desc: { type: STRING, comment: "描述" },
  contentId: { type: STRING, allowNull: false, unique: "contentId", comment: "内容 id,内容存储在 mongodb 中" },
  author: { type: STRING, allowNull: false, comment: "作者 username" },
  coverImg: { type: STRING, comment: "封面图片 url" },
  status: { type: STRING, allowNull: false, defaultValue: 1, comment: "状态:0-删除,1-未发布,2-发布,3-强制下线" },
  latestPublishAt: { type: DATE, defaultValue: null, comment: "最后一次发布的时间" }
});
Activity.belongsTo(UserModel, { foreignKey: "author", targetKey: "username" });
module.exports = Activity;

The author field in the activity table links to the username field in the user table, enabling queries for all activities created by a specific user.

Login and User Information

Login uses SMS verification via Tencent Cloud SMS. A 2‑minute cache prevents repeated sends; if the SMS fails to send, the cache is not set.

When a verification code is received, the server checks the cached code against the user input. If they match, the system looks up the user; existing users have their login time updated and receive a JWT token generated with jwt.sign . New users are created on‑the‑fly and also receive a JWT token. The token (prefixed with Bearer ) is placed in ctx.header.authorization and later verified with jwt.verify .

Note: When generating the token, a Bearer prefix is added; verification must strip this prefix using req.headers.authorization.split(" ")[1] to avoid JsonWebTokenError: invalid token .

Create Activity

The activity creation flow starts from a route , passes through a middleware for login checking, then a validator for input validation, and finally a controller that calls a service to interact with the databases.

/**
 * @description Activity related routes
 */
const router = require("koa-router")();
const loginCheck = require("../middlewares/loginCheck");
const validator = require("../middlewares/validator");
const { activityInfoSchema } = require("../validator/activity");
router.prefix("/activity");
router.post("/", loginCheck, validator(activityInfoSchema), async (ctx) => {
  const { username } = ctx.userInfo;
  const { title, desc, content = {} } = ctx.request.body;
  const res = await createActivity(username, { title, desc }, content);
  ctx.body = res;
});
module.exports = router;

The loginCheck middleware verifies the JWT token and populates ctx.userInfo . If verification fails, a predefined error response is returned.

module.exports = async function loginCheck(ctx, next) {
  const errRes = new ErrorRes(loginCheckFailInfo);
  const token = ctx.header.authorization;
  if (!token) { ctx.body = errRes; return; }
  let flag = true;
  try {
    const userInfo = await jwtVerify(token);
    delete userInfo.password;
    ctx.userInfo = userInfo;
  } catch (error) {
    flag = false;
    ctx.body = errRes;
  }
  if (flag) await next();
};

Input validation uses JSON Schema with the ajv library. The activity schema requires a title and defines optional fields such as desc , coverImg , contentId , and a nested content object.

const activityInfoSchema = {
  type: "object",
  required: ["title"],
  properties: {
    title: { type: "string", maxLength: 255 },
    desc: { type: "string", maxLength: 255 },
    coverImg: { type: "string", maxLength: 255 },
    contentId: { type: "string", maxLength: 255 },
    content: {
      type: "object",
      properties: {
        _id: { type: "string", maxLength: 255 },
        components: { type: "array" },
        props: { type: "object" },
        setting: { type: "object" }
      }
    }
  }
};

The controller createActivity checks the title, generates a short UUID, and calls the service.

async function createActivity(author, data = {}, content = {}) {
  const { title } = data;
  if (!title) return new ErrorRes(createActivityFailInfo, "标题不能为空");
  const uuid = uuidV4().slice(0, 4);
  try {
    const newActivity = await createActivityService({ ...data, author, uuid }, content);
    return new SuccessRes(newActivity);
  } catch (error) {
    return new ErrorRes(createActivityErrorFailInfo);
  }
}

The service first creates the activity content in MongoDB, obtains the generated _id , then creates the activity record in MySQL with that contentId .

async function createActivityService(data = {}, content = {}) {
  const { components = [], props = {}, setting = {} } = content;
  const newContent = await ActivityContentModel.create({ components, props, setting });
  const { _id: contentId } = newContent;
  const newActivity = await ActivityModel.create({ ...data, contentId: contentId.toString() });
  return newActivity.dataValues;
}

Component Platform Backend Design

The component platform focuses on displaying reusable components. Main APIs:

POST /component – add component

GET /component/list – list components

GET /component/:id – component detail

PATCH /component/:id – update component

DELETE /component/:id – delete component

Data Table Design

Component Detail Table

Column

Type

Comment

component_id

int

Component ID

version

varchar

Version number

description

varchar

Description

author

varchar

Component author

coverImg

varchar

Component cover image

config

varchar

Component configuration

markdow

varchar

Component documentation

create_time

date

Creation time

update_time

date

Update time

Component List Table

Column

Type

Comment

id

int

Unique identifier

name

varchar

Component name

create_time

date

Creation time

update_time

date

Update time

frequency

int

Usage frequency

author

varchar

Component author

status

int

Status

The component APIs also follow a standard RESTful style.

C‑End Backend Design

The C‑end serves the final marketing H5 pages. Design steps:

Fetch page configuration (overall settings, component data) by page_id .

Determine activity timing and status to decide display logic.

Render the page on the server using the component library.

Server‑side rendering (SSR) is preferred for high traffic and fast first‑paint requirements. Unlike client‑side rendering, SSR returns a fully rendered HTML string, eliminating the need for the browser to download and execute large JavaScript bundles before displaying content.

Image source: "The Benefits of Server Side Rendering Over Client Side Rendering".

Client‑Side Rendering

The browser first receives an empty HTML page, then loads JavaScript and CSS, requests data via AJAX, and finally renders the DOM, causing a noticeable white‑screen period.

Server‑Side Rendering

SSR generates the complete HTML on the Node server using a bundle renderer, sends it to the browser, and then hydrates it with the client‑side JavaScript bundle for interactivity.

Vue SSR is used as an example: the source code is bundled into vue-ssr-server-bundle.json and vue-ssr-client-manifest.json . The server creates a renderer with createBundleRenderer and calls renderer.renderToString to produce the HTML string, which is sent to the browser. The client then hydrates this HTML with the generated JavaScript to make the page interactive.

For more details, refer to the previous articles on the necessity and principles of SSR for To‑C marketing pages.

architectureNode.jsMySQLJWTMongoDBServer-side RenderingRESTful API
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

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.