Frontend Development 17 min read

Breaking the CRUD Loop: Low‑Code, DSL, and Component‑Based Strategies for Front‑End Development

The article examines how low‑code platforms and domain‑specific languages (DSL) can transform repetitive CRUD development in front‑end engineering into a more abstract, maintainable workflow, highlighting their benefits, limitations, and practical Vue‑based implementations.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Breaking the CRUD Loop: Low‑Code, DSL, and Component‑Based Strategies for Front‑End Development

Like the myth of Sisyphus endlessly pushing a boulder, many front‑end developers feel trapped in repetitive CRUD cycles when building internal admin tools.

Low‑Code

In recent years low‑code platforms have become popular; they typically let users model data and generate CRUD pages with a single click. However, most platforms deliver a lot of hype with little substance, especially for complex business logic, and many projects fail due to insufficient resources or lack of long‑term maintenance.

💡 In this article, "low‑code" refers to the narrow sense of visual‑builder platforms.

Low‑code is attractive because it lowers the entry barrier and speeds up simple tasks, but it cannot replace the 20‑30% of development work that truly requires coding, nor can it easily handle continuous refactoring and evolving requirements.

Complex business logic often becomes more complicated when forced into low‑code. Translating intricate workflows into GUI‑based expressions can turn into a nightmare.

Software engineering is an ongoing evolution. Current low‑code tools provide little assistance for maintainability, verification, or reliable version control.

Additional drawbacks include vendor lock‑in, lack of standards, performance issues, limited reusability, security concerns, and poor observability.

Low‑code does have suitable scenarios, such as marketing pages, data dashboards, form engines, storefront decoration, or quick POCs—essentially temporary, non‑core business features.

💡 Some platforms now offer "code‑generation" capabilities that make limited extensions possible.

Intermediate Form

Is there a middle ground between visual low‑code platforms and hand‑written code? The answer is a domain‑specific language (DSL). A DSL abstracts the problem domain, making the concrete syntax less important.

💡 A DSL can be a tiny language (e.g., SQL, GraphQL), a JSON/YAML schema, or a language built on a meta‑language such as Ruby, Groovy, or Rust, leveraging the host language’s metaprogramming features.

Visually‑driven low‑code platforms are essentially visual DSLs; their limitations stem from the visual layer rather than the abstraction itself.

The long‑standing GUI vs. CLI debate illustrates that while GUIs excel for simple, repetitive tasks, command‑line interfaces provide higher expressive power for complex problems.

Therefore, the form of a DSL influences its expressive power, but the crucial factor is how well it abstracts the specific problem domain.

Abstraction Process

A typical CRUD page consists of forms and tables, each built from atomic "fields" that have an edit and a preview state. By extracting these fields as reusable "atoms" (called 原件 or Atomic ), we can treat them as the smallest building blocks of CRUD interfaces.

Each atom encapsulates:

Data type and validation (basic types, constraints, or business attributes).

Data preview.

Data entry following a value / onChange contract, which simplifies state management.

Composing atoms yields declarative form and table definitions that hide pagination, error handling, and other boilerplate.

Example table pseudo‑code:

# Create a table with columns Name, Created Time, and Status, searchable by Name and Created Time
Table(
  columns(
    column(名称, name, queryable=true)
    column(创建时间, created, data-range, queryable=true)
    column(状态, status, select, options=[{label: 启用, value: 1}, {label: 禁用, value: 0}])
  )
)

Example form pseudo‑code:

# Create a form with fields Name, Status, and Address
Form(
  item(名称, name, required=true)
  item(状态, status, select, options=[{label: 启用, value: 1}, {label: 禁用, value: 0}])
  item(地址, address, address)
)

Convention Over Configuration

To minimise boilerplate and communication overhead, front‑end teams should align with product, design, and back‑end specifications, establishing conventions for layout, UI style, validation rules, data formats, and common APIs (e.g., file upload, import/export).

These conventions can be baked into component libraries, enabling an "out‑of‑the‑box" experience.

Implementation Example

Using Vue 3 and Element‑UI, we built a component library that works with a concise DSL to rapidly create CRUD pages.

Table definition (TypeScript):

import { defineFatTable } from '@wakeadmin/components'

/** Table item type */
export interface Item {
  id: number
  name: string
  createDate: number
}

export const MyTable = defineFatTable<Item>(({ column }) => {
  return () => ({
    async request(params) { /* fetch data */ },
    async remove(list, ids) { /* delete */ },
    columns: [
      column({ prop: 'name', label: 'Name', queryable: true }),
      column({ prop: 'createDate', valueType: 'date-range', label: 'Created', queryable: true }),
      column({
        type: 'actions',
        label: 'Actions',
        actions: [{ name: 'Edit' }, { name: 'Delete', onClick: (t, r) => t.remove(r) }]
      })
    ]
  })
})

Form definition (TypeScript):

import { defineFatForm } from '@wakeadmin/components'
import { ElMessageBox } from 'element-plus'

export default defineFatForm<{ name: string; nickName: string }>(({ item, form }) => {
  return () => ({
    initialValue: { name: 'ivan', nickName: '狗蛋' },
    submit: async values => {
      await ElMessageBox.confirm('Confirm save')
      console.log('Saved', values)
    },
    children: [
      item({ prop: 'name', label: 'Account Name' }),
      item({ prop: 'nickName', label: 'Nickname' })
    ]
  })
})

Global configuration (TypeScript) to unify image upload and date‑range components:

import { provideFatConfigurable } from '@wakeadmin/components'
import { Message } from 'element-ui'

export function injectFatConfigurations() {
  provideFatConfigurable({
    aImagesProps: { action: '/upload' },
    aDateRangeProps: {
      rangeSeparator: 'to',
      startPlaceholder: 'Start date',
      endPlaceholder: 'End date',
      valueFormat: 'yyyy-MM-dd',
      shortcuts: [
        { text: 'Last week', onClick(p) { p.$emit('pick', getTime(7)) } },
        { text: 'Last month', onClick(p) { p.$emit('pick', getTime(30)) } },
        { text: 'Last 3 months', onClick(p) { p.$emit('pick', getTime(90)) } }
      ]
    }
  })
}

Other similar products include XRender, Ant Design ProComponents, and Baidu Amis, all of which use JSON or DSL to describe UI.

Conclusion

Low‑code’s efficiency comes from abstraction rather than the visual layer; the goal is to free developers from repetitive boilerplate and let them focus on business logic. A well‑designed DSL, combined with strong conventions and reusable atomic components, provides a "ready‑to‑move‑in" experience for the majority of admin scenarios while still allowing customisation for edge cases.

Abstracting patterns into reusable atoms reduces code churn.

Declarative DSLs keep the implementation concise and maintainable.

Consistent conventions across design, product, and back‑end maximize the benefits of abstraction.

frontendDSLlow-codeVuecomponent libraryCRUDabstraction
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.