Reading and Writing Data in Open Mercato
API routes receive requests, commands change business data, and query services read it. Understanding the path matters when adding validation, permissions or behavior that must happen after a save.
On this page
Start at the API route
makeCrudRoute combines common behavior such as input validation, scoped queries, access checks and command-backed writes. Its options and the registered handlers determine which parts run. Read the actual route before assuming a custom endpoint has the same behavior.
The route and command choose their extension points. A direct database write does not automatically follow this entire path.
Code and documentation
Follow one change
- Validate the request and verify that the caller may change the target record.
- Run the registered command and its configured hooks.
- Persist the change. DataEngine coordinates declared events and indexing for writes that use it.
- Read the result through the appropriate query path. Test when an updated record becomes visible in indexes.
Code and documentation
Reads and writes have different responsibilities
| Path | Responsibility |
|---|---|
| QueryEngine / ORM read | Find and return records. Apply the authorized scope and select fields the caller may see. |
| CommandBus + registered command | Execute a named business operation. The command implements its checks, save and optional log or undo behavior. |
| DataEngine | Coordinates persistence and configured side effects for writes routed through it. Direct ORM calls use their own logic. |
Code and documentation
A command is a good place for a business operation
For example, closeServiceCase could verify required fields, save the status and return the case identifier. A route, import or AI tool can call the same operation instead of implementing three different versions of its rules. Every caller still supplies the correct authorization context.
Command hooks support preparing a snapshot, building a log entry and implementing undo. The author chooses and implements these hooks. An undo that restores a database row cannot recall an email already delivered or automatically cancel an external payment.
Code and documentation
A tenant identifier is not an access check
withScopedPayload fills tenantId and organizationId, preferring values supplied in the payload. It checks that values exist. The calling route or command must check the user’s access to the tenant, organization and target record. parseScopedCommandInput also rejects a foreign tenant for non-superadmins, but does not replace organization authorization.
Code and documentation
Test these two failure cases
- Try a valid record identifier from a different organization. The route should refuse access.
- Check lookups for encrypted fields. Non-deterministic ciphertext cannot be searched by its plaintext value; use a supported hash lookup or another identifier.