Skip to main content

Form Requests & API Resources

Validation lives in backend/app/Http/Requests/Api/V1/. Response shapes live in backend/app/Http/Resources/Api/V1/.

Form Requests

A request class per write endpoint. The authorize() method is rarely used (returns true) because authorization is enforced earlier by require.permission middleware + later by policies. Validation rules consistently:

  • Restrict Rule::exists() lookups to the active tenant: Rule::exists('clients','id')->where('tenant_id', $tenantId).
  • Cap free-text fields at sensible string lengths.
  • Validate enums against the constants on the matching model (e.g. Rule::in(Tenant::SUPPORTED_BUSINESS_TYPES)).

Catalog

FileUsed byNotes
Auth/RegisterOwnerRequest.phpAuthController@registerOwner
Auth/PasswordResetRequest.php (request + reset)password reset flow
StoreClientRequest, StorePetRequest, UpdatePetRequestclinic patient onboarding
StoreAppointmentRequest, UpdateAppointmentRequest, CancelAppointmentRequestappointment CRUD + cancel
StoreInvoiceRequest, RecordPaymentRequestbilling
RefundPaymentRequestinvoice refund
StoreVisitRequest, UpdateVisitRequestclinical
StorePrescriptionRequest, UpdatePrescriptionRequestclinicalnested items[] validation
StoreLabResultRequest, UpdateLabResultRequestclinical
StoreMedicalAttachmentRequestclinicalmime-type whitelist
StorePetVaccinationRequest, UpdatePetVaccinationRequestshared owner / clinic
StorePetHealthLogRequestshared
StorePetDocumentRequestowner
StorePetCaregiverRequestowner
StoreOrderRequest, UpdateOrderRequestowner + seller
StoreReminderRequestclinic
StoreReminderOptOutRequestclinic
StoreMedicineRequest, UpdateMedicineRequestinventory
StoreStockMovementRequestinventorytype-conditional rules
StoreSupplierRequest, UpdateSupplierRequestinventory
StorePurchaseOrderRequest, UpdatePurchaseOrderRequest, ReceivePurchaseOrderRequestinventory
Admin/StoreRoleRequest, UpdateRoleRequest, AssignRoleRequestadmin
Tenant/UpdateTenantSettingsRequestclinic settingscurrency + timezone enum check

Patterns to follow

  • Always restrict exists rules to the active tenant.
  • For id fields the request shouldn't accept (e.g. tenant_id, owner_user_id), strip them out — don't validate them. The audit calls out several cases where the request validates a field instead of ignoring it, and the controller picks the validated value.
  • Cross-field rules (e.g. subtotal + tax - discount === grand_total) belong on the request so the controller can stay thin.

API Resources

Resources shape the JSON response. Convention: one Resource per public model, narrower variants for public endpoints.

Catalog

FileReturns
UserResourceid, name, email, account_type, current tenant id, permissions list
TenantResourcefull editable tenant (used by clinic settings)
ProviderListingResourcenarrow: id, name, slug, business_type, phone, address, lat/lng, headline, bio, services, optional distance_km
ClientResource, PetResourcefull clinical view
PetSummaryResourceminimal — used in lists / timeline
AppointmentResource(audit: drop tenant_id)
InvoiceResource, PaymentResourcefull money view
VisitResource, PrescriptionResource, PrescriptionItemResource, LabResultResource, MedicalAttachmentResourceclinical
PetVaccinationResource, PetHealthLogResource, PetDocumentResource, PetShareResource, PetCaregiverResourceowner-side
OrderRequestResourceowner + seller views
ReminderResource, ReminderTemplateResource, OptOutResourcenotifications
MedicineResource, StockMovementResource, SupplierResource, PurchaseOrderResource, PurchaseOrderItemResourceinventory
RoleResource, PermissionResource, AuditLogResourceadmin

Conditional fields

distance_km on ProviderListingResource uses the $this->when(isset($this->distance_km), …) pattern so it only appears when the geo filter is active.

receipt_pdf_url on InvoiceResource is similarly conditional on receipt_emailed_at being set.

The audit recommends formalising three tiers for resource classes to reduce accidental field leakage:

  • Public — narrow, marketing-safe. Today's ProviderListingResource is the right shape.
  • Scoped — for tenant members. Today's main resources.
  • Internal — for system_admin / Filament. Currently overlapping with Scoped.

The audit lists specific fields to remove (e.g. tenant_id from AppointmentResource, internal disk names from attachment resources).