Article

CRUD, REST, and the Case for Explicit Commands

CRUD and REST interfaces are centered on manipulating resources or fields, not on expressing business actions. Collaborative systems rely on explicit commands such as submit, approve, reject, or escalate, each with rules, permissions, preconditions, and side effects. This article explores why explicit command interfaces are essential for building collaborative systems.

Jorge Leal Portela
Jorge Leal PortelaNovember 23, 2025
Shape sorter toy showing CRUD blocks (R, U, D) and business action blocks (Submit, Approve, Reject) scattered around, illustrating how CRUD operations don't fit business actions

CRUD (Create, Read, Update, Delete) and REST have become the default patterns for building APIs. They work well for simple data manipulation: create a record, update a field, delete an item. But collaborative systems operate differently. They don't just manipulate data—they execute business actions with meaning, intent, and consequences.

When you submit a document, approve a request, or escalate a case, you're not just updating fields. You're triggering a state transition, validating permissions, checking preconditions, emitting events, and potentially affecting other parts of the system. CRUD and REST lack native representations for these actions, forcing teams into awkward workarounds that push logic to the wrong places.

This article explores why explicit command interfaces are essential for collaborative systems and how they create clearer workflows, simpler UIs, and APIs that express intent rather than just data changes.

The Problem with CRUD and REST

Resource-centric, not action-centric

CRUD and REST are built around resources and their fields. You update a document by sending a PATCH request with field changes. You delete a record by sending a DELETE request. But what if you want to "submit" a document? Or "approve" a request? Or "escalate" a case?

These actions don't map cleanly to CRUD operations. Teams often resort to:

  • PATCH updates with special fields: PATCH /documents/123 {"status": "submitted"}. This looks like a field update but is actually a state transition with rules and side effects.
  • Verb-like endpoints: POST /documents/123/submit. This works but feels like a workaround, and REST purists might object.
  • Action fields in update payloads: PATCH /documents/123 {"action": "submit"}. This mixes data updates with action execution.

None of these approaches feel natural because they're trying to force actions into a resource-centric model.

Logic pushed to the frontend

When actions are forced into PATCH updates or special fields, the frontend often becomes responsible for:

  • Determining valid transitions: The UI must know which status values are allowed in the current state
  • Enforcing permissions: The frontend decides which actions to show or hide based on user roles
  • Validating preconditions: The UI checks if required fields are filled before allowing an action
  • Handling workflow rules: The frontend orchestrates multi-step workflows

This creates several problems:

  • Duplication: Business logic exists in both frontend and backend, and they can drift out of sync
  • Security gaps: Frontend validation can be bypassed; the backend must still validate everything
  • Complexity: The frontend becomes a workflow engine rather than a presentation layer
  • Maintenance burden: Changes to business rules require updates in multiple places

Backend as passive repository

When the backend only accepts field updates, it behaves like a passive repository. It stores whatever the frontend sends, validates field formats, but doesn't govern behavior. The backend doesn't know that "submitting" a document is different from "updating" its status field.

This creates a fundamental mismatch: collaborative systems need the backend to be an active governor of behavior, enforcing transitions, validating permissions, and managing side effects. But CRUD/REST encourages a passive model where the backend just stores data.

The result is that critical business logic—what actions are allowed, who can perform them, what preconditions must be met—ends up scattered across frontend code, API middleware, and database constraints, making it hard to reason about and maintain.

Explicit Command Interfaces

Commands express intent

Explicit command interfaces treat actions as first-class citizens. Instead of updating fields, you send commands:

POST /commands/submit-document
POST /commands/approve-request
POST /commands/escalate-case

Each command has:

  • Explicit name: The command name clearly expresses the business action
  • Parameters: The data needed to execute the command (document ID, reason, etc.)
  • Rules: Validation, permissions, and preconditions are part of the command definition
  • Side effects: Commands can trigger events, notifications, and other system changes

Benefits of explicit commands

Server validates state

The server can validate that the current state allows the command. It knows that a document can only be submitted if it's in "Draft" state, not just because a field was updated.

Server enforces transitions

The server controls which state transitions are allowed. It enforces the state machine rules, preventing invalid transitions regardless of what the frontend sends.

Server emits events

Commands can emit events that other parts of the system react to. When a document is submitted, an event is emitted that triggers notifications, updates projections, or starts workflows.

Centralized rules

All business rules—permissions, validations, preconditions—are centralized in the command handlers. The frontend doesn't need to know these rules; it just presents available commands and sends them when the user acts.

Simpler, clearer UIs

When the backend exposes explicit commands, the frontend becomes simpler:

  • Query available commands: The UI can ask the server "what commands are available for this document?" and display buttons accordingly
  • Send commands directly: When the user clicks "Submit," the UI sends the submit command—no need to figure out which fields to update
  • Handle results: The command returns success or a clear error message, making error handling straightforward
  • No workflow logic: The UI doesn't need to know state machines, transitions, or business rules

The UI becomes a presentation layer that queries available commands and sends them when users act. All the complexity stays on the server where it belongs.

APIs that express intent

Command interfaces make APIs self-documenting. When you see POST /commands/submit-document, you immediately understand what it does. Compare that to PATCH /documents/123 {"status": "submitted"}, which requires reading documentation to understand the implications.

Commands also make it easier to:

  • Audit actions: Command logs clearly show what actions were taken, not just what fields changed
  • Debug issues: When something goes wrong, you can see exactly which command was sent and why it failed
  • Test behavior: You can test commands in isolation, verifying that they enforce rules and produce correct side effects
  • Version APIs: Commands can be versioned independently, allowing gradual migration

Commands in the State-Based Collaboration Framework

Actions as explicit commands

In the State-Based Collaboration Framework, actions are explicit commands. Each action has:

  • A name: Clearly expresses the business intent (SubmitDocument, ApproveRequest, EscalateCase)
  • Parameters: The data needed to execute the action
  • State reducer: A pure function that validates the action, checks permissions, evaluates guards, and computes the new state
  • Event emission: Actions generate events that are recorded and can trigger side effects

The framework ensures that actions are evaluated in the context of the current state, with all rules and permissions checked before any state change occurs. This makes the system deterministic, testable, and auditable.

No CRUD confusion

By using explicit actions, the framework avoids the CRUD/REST problems:

  • No field updates masquerading as actions: You don't update a status field; you execute a Submit action
  • No logic in the frontend: The frontend queries available actions and sends them; all business logic is on the server
  • No passive repository: The backend actively governs behavior, enforcing state machines, validating permissions, and managing transitions
  • Clear intent: Every API call expresses a business action, making the system easier to understand and maintain

Related content

This article connects to:

Explore more articles

This article is part of a series exploring collaboration patterns and practices. Check out other articles or dive into the framework components.