FriendChise Docs
Services and Actions
Why FriendChise splits business logic from server-action boundaries
FriendChise splits write flows into two layers on purpose.
Services
lib/services/*is the reusable domain layer.- Services hold business rules, database work, transactions, inheritance logic, and audit logging.
- Services should not know about forms, redirects,
revalidatePath, or request-specific UI state. - They are the right place for logic that should be shared across server actions, tests, or other services.
Actions
app/actions/*is the request boundary between the UI and the service layer.- Actions authenticate the caller, check permissions, validate raw input, and normalize browser payloads like
FormData. - Actions also handle request-specific behavior such as demo limits, storage upload URLs,
revalidatePath, and redirects. - If the operation is only about a UI flow or a storage workflow, it can live in actions without needing a separate service.
Why both exist
- Services keep the actual business rules isolated and testable.
- Actions keep auth, input parsing, and page refresh behavior close to the UI.
- That split keeps domain code reusable and stops server-action concerns from leaking into the rest of the app.
Rule of thumb
- Put "what should happen" in a service.
- Put "who is allowed to do it" and "what does the UI need next" in an action.
Ownership-gated pages
- Some page surfaces are intentionally owner-only even before the action layer runs.
- The announcements list page follows this pattern:
requireOrgOwnerPage()gates the route, and the list UI exposes owner-only edit, delete, and expiry actions. - Use this pattern when the page contains direct mutation controls that should never be visible to non-owners.
Examples
createTaskActionparsesFormData, validates it, checks permissions, then callscreateTask.createOrgaction authenticates, validates the payload, then calls the org service.- Storage actions handle signed URLs and file paths because those are request-bound workflows.
