1. The Fundamental Principle

If it matters → it must be enforced on the backend.
Frontend validation → improves UX.
Backend validation → enforces correctness and security.
Both must exist. Backend is never optional.

A frontend developer can bypass browser validation in seconds. A user can open DevTools, modify a request, or call your API directly. If the backend does not enforce the rule — the rule does not exist.

2. What Belongs on the Backend (Always)

These must never be frontend-only:

Logic Platform Why
Authentication verification Supabase / Xano / Django Anyone can fake a logged-in state on frontend
Role and permission checks Supabase RLS / Xano / Django Roles can be faked or manipulated on frontend
Input validation (required, format, length) Supabase / Xano / Django Frontend validation can be bypassed
Business rules (status transitions, eligibility, limits) Supabase / Xano / Django These are invariants — frontend cannot guarantee them
Price and discount calculations Xano / Django Sending price from frontend means users can manipulate it
Ownership verification ("can this user access this record?") Supabase RLS / Xano / Django Frontend only knows what it's told
Quota and rate limit enforcement Xano / Django Never trust frontend counts
Soft delete / hard delete rules Supabase / Xano / Django Frontend can hide a button — it cannot stop a direct API call
File type and size validation Supabase / Xano / Django Frontend file checks are easily bypassed
Sensitive data masking (API keys, tokens, PII) Supabase / Xano / Django Never compute on frontend and send masked output
Audit log creation Supabase / Xano / Django Frontend can skip calls — backend triggers cannot

3. What Frontend Is Allowed to Do (Display + UX Only)

Frontend Responsibility Allowed? Notes
Show/hide buttons based on known user role Yes Still enforced on backend
Disable form fields based on user state Yes Still validated on backend
Client-side form validation before API call Yes Reduces unnecessary API calls
Display loading/error/success states Yes Core frontend responsibility
Format dates, numbers, currencies for display Yes Display-only, no backend impact
Cache data locally for performance Yes Always re-validate on backend when mutation happens
Compute totals for display only (shopping cart preview) Yes Backend must recompute on submit
Show permission-based UI (hide invite button for viewers) Yes Backend still enforces on the API call

4. Anti-Patterns — Never Do These

Sending Role From Frontend and Trusting It

Bad:

POST /projects
{
  "name": "New Project",
  "user_role": "admin"
}

The backend then uses user_role from the request to decide what the user can do.

Any user can send "user_role": "admin" and bypass all restrictions.

Good:

Backend looks up the user's role from the database using auth.uid() / auth token.
Role is never accepted from the frontend payload.

Doing Permission Checks Only in Frontend

Bad:

// Frontend hides the button — that's all
if (user.role !== 'admin') {
  return null; // hide button
}

The API has no role check. Anyone who calls the API directly becomes an admin.

Good:

Frontend: Hide the button for non-admins (UX).
Backend: Check role on every API call, return 403 if not allowed (enforcement).
Both must exist.

Sending Computed Price From Frontend

Bad:

POST /checkout
{
  "items": [...],
  "total": 150.00,
  "discount": 20.00
}

The backend trusts the total and processes payment for 150.

Any user can send "total": 1.00 and get the product for 1 rupee.

Good:

Backend receives the item list and quantities.
Backend computes the price from its own database/pricing rules.
Backend charges the computed amount — never the frontend-sent amount.

Filtering Sensitive Data on Frontend

Bad:

// Backend returns all user data, frontend filters out private fields
const { password_hash, secret_token, ...safeUser } = user;

The browser received the sensitive fields. They appear in network requests.

Good:

Backend only returns fields the user is allowed to see.
Sensitive fields are never included in the API response.
Frontend receives only what it needs.

Skipping Backend Validation Because Frontend Already Validates

Bad:

# Django view
def create_project(request):
    # "Frontend validates this, so we trust it"
    name = request.data.get('name')
    Project.objects.create(name=name, workspace=workspace)

Good:

def create_project(request):
    name = request.data.get('name', '').strip()
    if not name or len(name) < 2:
        return JsonResponse({'code': 'VALIDATION_ERROR', 'message': 'Name is required.'}, status=422)
    if len(name) > 100:
        return JsonResponse({'code': 'VALIDATION_ERROR', 'message': 'Name too long.'}, status=422)
    Project.objects.create(name=name, workspace=workspace)

Letting Frontend Control Admin Actions via URL Parameters

Bad:

GET /admin/users?role=admin

Backend promotes users to admin based on the URL parameter.

Good:

Backend checks if the calling user is an admin.
Backend performs the action using its own logic.
URL parameters carry only resource identifiers, not permissions.

5. Platform-Specific Rules

Supabase







Xano






Django






WeWeb / React (Frontend)





6. The Backend Logic Decision Test

Before writing any logic, ask:

"If a technically capable user bypassed the frontend and called this API directly — would they be able to do something they should not be able to do?"

If the answer is yes — the backend is missing a check.

Run this test for every:

  • Permission rule
  • Validation rule
  • Calculation
  • Status transition
  • Data access

7. Where Logic Lives By Platform

Logic Type Supabase Xano Django Frontend
Who can read this record RLS SELECT policy Auth + Permission check View + QuerySet filter Display only
Who can create this record RLS INSERT policy Auth + Permission check + Validation Form + Permission class UI gate only
Who can edit this record RLS UPDATE policy Auth + Permission check View + Permission class UI gate only
Who can delete this record RLS DELETE policy Auth + Permission check View + Permission class Confirm dialog only
Input validation DB constraints + RPC Preconditions Form / Serializer UX hint only
Price calculation RPC function Custom function Service function Display preview only
Business rule enforcement RLS + RPC Xano function Service / Model method Not allowed
Audit logging DB trigger / RPC Post-operation step Django signal / middleware Not allowed
File validation Storage policy Preconditions View / Form UX hint only

8. Summary Rules

1. Backend is the source of truth — always.
2. Frontend validation is UX — not security.
3. Role is read from the database — never from the request.
4. Price is computed on the backend — never sent from frontend.
5. Ownership is verified by the backend using the authenticated user's ID.
6. Service keys and secrets never touch frontend code.
7. Hiding a button is not the same as enforcing a permission.
8. Frontend can bypass any UI check — backend cannot be bypassed.
9. If it matters, enforce it in two places: backend (required) and frontend (UX).
10. When in doubt: the backend decides, the frontend displays.