0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • Introduction
  • Pitch
  • Hogwarts
  • Live Demo
  • MVP
  • Roadmap
  • Launch Sprint
  • PRD
  • Get Started
  • Localhost
  • Architecture
  • Structure
  • Pattern
  • Page
  • Layout
  • Content
  • Types
  • Config
  • Actions
  • Queries
  • Authorization
  • Validation
  • Form
  • Table
  • Detail
  • Card
  • Util
  • Hooks
  • List Params
  • Views
  • README.md
  • ISSUE.md
  • Technology Stack
  • Database
  • File
  • CDN Assets
  • Entry Points
  • Dashboard
  • Authentication
  • Credentials
  • OAuth
  • Flow Diagrams
  • Multi-Tenancy
  • Offline
  • Onboarding
  • Onboarding Videos
  • Add Values
  • Admission
  • Application
  • Attendance
  • Compliance
  • Profile
  • Exams
  • Exam Wizard
  • Timetable
  • Classrooms
  • Notifications
  • Conference
  • LMS (Lumos)
Finance
  • Finance
  • Fee Management
  • Invoice
  • Wallet
  • Salary
  • Payroll
  • Timesheet
  • Expenses
  • Budget
  • Receipt
  • Accounts
  • Banking
  • Reports
  • Dashboard
  • Permissions
  • Messages
  • Integration Flow
  • Provision
  • AI Document Processing
  • Document Intelligence
  • Internationalization
  • Translation
  • Translation Guide
  • Icons
  • Docs Factory
  • Inspiration
  • Listings
  • Teachers
  • Students
  • Catalog
  • Library
  • Contributing
  • Code of conduct
  • GitHub Workflow
  • Database Seeds
  • Database Safety
  • Test Accounts
  • Playwright
  • Prettier
  • Block Rebound
Sales & GTM
  • Marketing Brief
  • Sales
  • Go-to-market
  • Marketing
  • Admission — Feature Spotlight
  • Pilot Program
  • Leads
  • Proposal
  • Outreach Templates
  • Case Study
  • Competitors
  • Landing-page teardown
  • Competitor FAQ
  • Business model
  • Shared economy
  • Traction
Fundraising & Ecosystem
  • Get Support
  • Investor Deck
  • Data Room
  • Investors
  • Accelerators
  • Incubators
  • Grants
  • Sponsors
  • Partners
  • Competitions & Hackathons
  • Universities & Training Centers

Actions

PreviousNext

Server actions — auth, tenant, permission, validate, execute, revalidate.

actions.ts runs on the server and mutates state. Every action follows the 5-step flow: authenticate → tenant → permission → validate → execute + revalidate. Reads belong in queries.ts.

Categories

CategoryShapeReference
CRUDCreate / read / update / delete (200–1,300 lines)listings/grades/actions.ts
Form stepValidate one wizard step, no DB writeapply/personal/actions.ts
IntegrationExternal API wrappers (Stripe, AI, email)billing/actions.ts
SaaS adminDEVELOPER-only platform operationssaas-dashboard/tenants/actions.ts
AuthPre-tenant; uses singular action.tsauth/login/action.ts
File / uploadSigned URL minting, S3 confirmsfile/upload/actions.ts

Rules

#Rule
1"use server" at the top
2The 5-step flow — auth → tenant → permission → validate → execute + revalidate
3Import ActionResponse from @/lib/action-response, never redefine
4Validate with Zod schemas from validation.ts — never raw FormData
5Mutations only — reads in queries.ts
6try/catch with structured errors — return errorCode, never throw
7Include schoolId in every query
8Stay under ~300 lines — split into actions/ subdirectory by capability

Canonical example — the 5 steps

"use server"
 
import { revalidatePath } from "next/cache"
import { auth } from "@/auth"
 
import type { ActionResponse } from "@/lib/action-response"
import { db } from "@/lib/db"
import { getTenantContext } from "@/lib/tenant-context"
 
import { canCreate } from "./authorization"
import { subjectCreateSchema, type SubjectCreateInput } from "./validation"
 
export async function createSubject(
  input: SubjectCreateInput
): Promise<ActionResponse<{ id: string }>> {
  try {
    // 1. authenticate
    const session = await auth()
    if (!session?.user?.id) return { success: false, errorCode: "UNAUTHORIZED" }
 
    // 2. tenant
    const { schoolId } = await getTenantContext()
    if (!schoolId) return { success: false, errorCode: "MISSING_TENANT" }
 
    // 3. permission
    if (!canCreate(session.user.role)) {
      return { success: false, errorCode: "FORBIDDEN" }
    }
 
    // 4. validate
    const parsed = subjectCreateSchema.safeParse(input)
    if (!parsed.success) {
      return { success: false, errorCode: "INVALID_INPUT" }
    }
 
    // 5. execute and revalidate
    const subject = await db.subject.create({
      data: { ...parsed.data, schoolId },
      select: { id: true },
    })
    revalidatePath("/subjects")
    return { success: true, data: { id: subject.id } }
  } catch (error) {
    console.error("createSubject error", error)
    return { success: false, errorCode: "SERVER_ERROR" }
  }
}

Error codes

Return codes; the dictionary maps them on the client via useI18nMessages(dictionary).error.

CodeMeaning
UNAUTHORIZEDauth() failed
MISSING_TENANTNo schoolId on the session
FORBIDDENPermission check failed
INVALID_INPUTZod validation failed
NOT_FOUNDRecord doesn't exist or other tenant
CONFLICTUnique constraint violation
SERVER_ERRORUnexpected exception
type ActionResponse<T> =
  | { success: true; data: T }
  | { success: false; errorCode: ErrorCode; error?: string }

The error field is for developer diagnostics only — never show to users.

Anti-patterns

  • Redefining ActionResponse (27+ files) — import from @/lib/action-response.
  • Monolith files — 5,692-line timetable/actions.ts is the canonical bad example. Split into actions/.
  • Duplicate function names across files (createPeriod, getInvoices).
  • console.log of auth data — never log emails, tokens, sessions.
  • Raw FormData.get() as string — parse with Zod first.
  • any types (150+ in codebase) — use inferred Zod or Prisma payloads.
  • Read functions in actions.ts — move to queries.ts.

Naming

  • File: actions.ts (plural). action.ts (singular) only for legacy auth flows.

Sibling roles

  • Imports schemas + types from validation.ts.
  • Imports permission helpers from authorization.ts.
  • Never imports from form.tsx / table.tsx.
  • Never imports from another feature's actions.

See also

  • Pattern
  • Validation
  • Authorization
  • Queries
ConfigQueries

On This Page

CategoriesRulesCanonical example — the 5 stepsError codesAnti-patternsNamingSibling rolesSee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.