Admin CRUD Mandates
When implementing list / table / query features, the following are mandatory defaults. Apply them without asking, even if the request only says "做一个列表页".
After implementing, your response summary MUST explicitly list which defaults were applied (pagination, keyword search, audit log, transaction, permission checks, etc.) so the user can opt out if a default doesn't fit their edge case.
If the user's request directly contradicts a rule, confirm once; if they insist, leave a code comment explaining why the default was skipped.
Rule 1 — Pagination is mandatory
Any API or page returning a list MUST paginate. No exceptions for "就几条数据".
Backend
- Accept
page (default 1) and pageSize (default 20, hard cap 100)
- Return shape:
{ list: T[], total: number, page: number, pageSize: number }
- Apply
LIMIT/OFFSET (or cursor) at the DB layer — never fetch-all-then-slice
- Whitelist
sortBy fields to prevent injection
- Add DB index on the default sort column
Frontend
- Pager control with current page + total pages + total count
- pageSize selector
[10, 20, 50, 100]
- Sync
page and pageSize to URL query
- Reset to page 1 when keyword/filters change
Skip only if ALL true
- Dataset is bounded small (<100 rows) forever by business rule
- Purely static config/reference data
- User explicitly says "no pagination"
Rule 2 — Keyword search is mandatory
Any list of user/business records MUST have keyword search.
Backend
- Accept
keyword query param; trim input; ignore empty
- Define explicit searchable fields in the handler (not "all columns")
- Index-friendly query:
- Small/medium table →
LIKE 'keyword%' on indexed columns
- Large table → full-text index or search engine
- Combine with pagination + filters via
AND
Frontend
- Search input above the table
- Debounce 300ms
- Sync
keyword to URL query
- Reset page to 1 on keyword change
- Clear button to reset
Skip only if
List is <20 rows of pure static config.
Rule 3 — Default sorting
Every list MUST have a deterministic default sort. If the user does not specify, use createdAt DESC. Pagination without a deterministic ORDER BY produces duplicate/missing rows across pages.
Rule 4 — Loading / empty / error states
Every list page MUST render all three:
- Loading: skeleton or spinner while fetching
- Empty: friendly message + primary CTA when
total === 0
- Error: message + retry button on request failure
Do not ship "only the happy path".
Rule 5 — Logging is mandatory
Every API endpoint and every user-facing mutation MUST leave a trace. "没日志" = 线上出事只能靠猜。
Request log (every HTTP handler)
- Structured log (JSON), fields:
method, path, userId, duration_ms, status, traceId
- Generate
traceId at HTTP entry if not present; propagate it to DB calls, outbound HTTP, enqueued jobs, and queue message headers — every downstream log line must carry it
- Use log levels deliberately:
debug / info / warn / error — not everything at info
Business-mutation log (every create / update / delete)
- Record: who (
userId), what entity (type + id), which action, when
- For updates: log the diff, or at minimum the changed field names
- For sensitive operations, write to a dedicated
audit_log table (separate from app logs, longer retention):
- permission / role changes
- delete (including soft delete)
- money / balance / payment changes
- data export
Error log
- Catch at top-level middleware; log stack trace + sanitized inputs +
userId + traceId
- No empty
catch {} — never swallow exceptions silently
Other backend log points (same structured-log rules apply)
- Outbound calls: log target, method,
duration_ms, status, retry count, traceId; mask sensitive headers/body
- Database: log slow queries (threshold per project); use parameter placeholders — never inline PII into the SQL string
- Background jobs / workers / queue consumers: log
jobName, jobId, start/end/duration_ms/status/traceId; failures with stack + retry count; DLQ entries at error
Never log
- Passwords, tokens, API keys, session cookies
- Full card numbers / ID numbers — mask them
- Raw request body if it contains any of the above
Frontend
- Report unhandled errors + unhandled promise rejections to an error-tracking service (Sentry or equivalent)
- Strip
console.log noise from production builds
- When an API call fails, log endpoint + params + status for debugging
Skip only if
Never. Even a throwaway demo needs a request log and an error log.
Rule 6 — Permission checks (three levels)
Every CRUD operation MUST verify access at all three layers. AI commonly does only layer 1 and ships privilege-escalation bugs.
- Route / page level: can this user reach this page at all?
- API handler level: the handler MUST re-check role/permission — never trust the frontend gate
- Row level: for any record access (view / edit / delete), verify the user owns it or has explicit grant (multi-tenant isolation, team scoping, ownership)
For write operations, check permission before loading data and again before persisting — TOCTOU gaps are real.
Skip only if
Public-facing, read-only endpoint with no data-isolation requirement.
Rule 7 — Transactions & idempotency
Multi-table writes MUST run in a transaction
- Create with related records (user + profile + default role)
- Update that also writes an audit row
- Delete that cascades or cleans up related data
Without a transaction: partial failures leave orphans / dangling state.
Create endpoints MUST be idempotent — pick one
- Client-supplied idempotency key (recommended for external API surfaces)
- Business-level unique constraint (e.g.
UNIQUE(userId, resourceId))
- Deduplication window keyed on meaningful business fields
Users double-click submit. Networks retry. Without idempotency you get duplicate records.
Update endpoints MUST guard against concurrent modification
- Client sends version /
updatedAt along with the payload
- Server rejects on mismatch with
409 Conflict — do NOT silently overwrite
Skip only if
The operation is genuinely single-row, single-table, non-financial, and retry-safe by nature.
How to apply
- Detect the trigger: user asks for a list / table / query / admin / 后台 / 列表 / 查询 / CRUD feature, or any mutation touching business data.
- Implement Rules 1–7 unconditionally.
- If you are about to write a list endpoint, a mutation, or a component without any of these, stop and add them.
- Before declaring "done", confirm all seven rules are present end-to-end (API + UI + DB index + logs + permissions + transaction).
- In your response summary, explicitly list the defaults applied in a short bullet list, e.g.:
> Applied by default: ✓ pagination (page/pageSize) ✓ keyword search on name,email ✓ audit log on delete ✓ row-level ownership check ✓ transaction around user+profile create.
This lets the user spot and opt out of any default that does not fit.
What NOT to do
- ❌ Ask "do you need pagination?" — just add it
- ❌ Return an unpaginated array because "demo 数据少"
- ❌ Add a search input on the frontend but forget the
keyword param in the API
- ❌ Paginate without a deterministic
ORDER BY
- ❌ Ship the list without loading / empty / error states
- ❌ Fetch all rows and filter on the frontend
- ❌ Empty
catch {} that swallows exceptions
- ❌ Logging passwords / tokens / PII in plaintext
- ❌ Deleting a record with no audit trail
- ❌ Only
console.log — no structured server logs
- ❌
traceId only on the HTTP handler, lost across DB / outbound / job calls
- ❌ Background jobs / cron tasks running silently with no start/end/failure log
- ❌ Permission check only at the route level, not re-checked in the API handler
- ❌ Relying on frontend to hide a button as a "permission" — backend still accepts the call
- ❌ Multi-table write without a transaction — partial failure leaves dangling rows
- ❌ Create endpoint with no idempotency — user double-clicks submit → two records
- ❌ Update endpoint with no version check — last write silently clobbers concurrent edits
- ❌ Implementing defaults silently without listing them in the response summary