Skip to main content

Overview

Guard functions are serverless Appwrite functions that act as validation and moderation layers between client applications and the database. They enforce business logic, rate limiting, authorization, and data validation before allowing operations to proceed. All guard functions:
  • Run on Node.js 22 runtime
  • Have 15 second timeout
  • Use s-0.5vcpu-512mb specification
  • Execute synchronously (async: false)
  • Require user authentication via x-appwrite-user-id header

Function List

FunctionPurposeRate Limit
posts-guardManage post creation, updates, deletion, likes, and views10 req/min per action
comments-guardManage comment operations60 req/min per action
rooms-guardManage chat room lifecycle10 req/min per action
room-message-guardHandle live chat messages60 req/min
notifications-guardFetch user notificationsNo rate limit
leaderboard-guardRetrieve user rankingsNo rate limit
user-push-token-guardManage push notification tokensNo rate limit

Common Configuration

All guard functions share these configuration settings from appwrite.config.json:
{
  "runtime": "node-22",
  "specification": "s-0.5vcpu-512mb",
  "execute": ["any"],
  "scopes": [
    "users.read",
    "tables.read",
    "tables.write",
    "rows.read",
    "rows.write"
  ],
  "timeout": 15,
  "enabled": true,
  "logging": true,
  "entrypoint": "src/main.js",
  "commands": "npm install"
}

posts-guard

Function ID: 6967383500111e42ec97

Purpose

Manages all post-related operations including creation, updates, deletion, likes, and view tracking with rate limiting and authorization.

Actions

create

Creates a new post with user validation and rate limiting. Rate Limit: 10 requests per minute per user Request Body:
{
  "action": "create",
  "content": "string (required)",
  "images": ["string"] // optional array
}
Permissions Set:
  • Read: All authenticated users
  • Update: Post author only
  • Delete: Post author only

update

Updates existing post content and images. Rate Limit: 10 requests per minute per user Authorization: Must be post author Request Body:
{
  "action": "update",
  "postId": "string (required)",
  "content": "string (required)",
  "images": ["string"] // optional
}

delete

Removes a post from the database. Rate Limit: 10 requests per minute per user Authorization: Must be post author Request Body:
{
  "action": "delete",
  "postId": "string (required)"
}

like

Toggles like status on a post (like/unlike). Rate Limit: None Request Body:
{
  "action": "like",
  "postId": "string (required)"
}
Behavior:
  • If user already liked: Decrements likes count and removes user from likedBy
  • If user hasn’t liked: Increments likes count and adds user to likedBy

view

Records a unique view from a user. Rate Limit: None Request Body:
{
  "action": "view",
  "postId": "string (required)"
}
Behavior:
  • Only increments view count once per user (checks viewedBy array)
  • Does not increment if user already viewed the post

Client Usage

From services/posts.service.ts:
export async function executePost({
  action,
  postId,
  content,
  images,
}: {
  action: "create" | "update" | "delete" | "like" | "view";
  content?: string;
  postId?: string;
  images?: string[];
}) {
  const execution = await functions.createExecution({
    functionId: POSTS_GUARD_FUNCTION_ID,
    body: JSON.stringify({ action, content, postId, images }),
    async: false,
  });

  if (execution.status === "failed") {
    throw new Error(`Post execution failed ${execution.errors}`);
  }

  return execution;
}

comments-guard

Function ID: 697231ae0003e880b55b

Purpose

Handles comment creation, updates, and deletion with validation and rate limiting.

Actions

add

Creates a new comment on a post. Rate Limit: 60 requests per minute per user Request Body:
{
  "action": "add",
  "postId": "string (required)",
  "content": "string (required, non-empty)"
}
Validation:
  • Content must be a non-empty string
  • Post ID must be provided and valid
Permissions Set:
  • Read: All authenticated users
  • Update: Comment author only
  • Delete: Comment author only

update

Modifies an existing comment. Rate Limit: 60 requests per minute per user Authorization: Must be comment author Request Body:
{
  "action": "update",
  "commentId": "string (required)",
  "content": "string (required, non-empty)"
}
Behavior:
  • Sets isEdited: true on the comment
  • Validates ownership before updating

delete

Removes a comment. Rate Limit: 60 requests per minute per user Authorization: Must be comment author Request Body:
{
  "action": "delete",
  "commentId": "string (required)"
}

Client Usage

From services/comments.service.ts:
export async function executeComment({
  action,
  postId,
  commentId,
  content,
}: {
  action: "add" | "update" | "delete";
  postId?: string;
  commentId?: string;
  content?: string;
}) {
  const execution = await functions.createExecution({
    functionId: COMMENTS_GUARD_FUNCTION_ID,
    body: JSON.stringify({ action, postId, commentId, content }),
    async: false,
  });

  if (execution.status === "failed") {
    throw new Error("Comment execution failed");
  }

  return execution;
}

rooms-guard

Function ID: 6966ec4f00039df360a0

Purpose

Manages cricket match chat rooms with time-based status validation.

Actions

create

Creates a new chat room for a cricket match. Rate Limit: 10 requests per minute per user Request Body:
{
  "action": "create",
  "teams": ["string", "string"],
  "startTime": "ISO 8601 datetime",
  "endTime": "ISO 8601 datetime (optional)",
  "matchType": "ODI" | "TEST" | "T20",
  "isLocked": boolean
}
Permissions Set:
  • Read: All authenticated users
  • Update: Room creator only
  • Delete: Room creator only

update

Updates room details. Rate Limit: 10 requests per minute per user Authorization: Must be room creator Validation:
  • Cannot update if room status is “finished”
  • Status is calculated based on current time vs start/end times:
    • finished: Current time > end time
    • upcoming: Current time < start time
    • live: Between start and end time
Request Body:
{
  "action": "update",
  "roomId": "string (required)",
  "teams": ["string", "string"] // optional
  "startTime": "ISO 8601 datetime (optional)",
  "endTime": "ISO 8601 datetime (optional)",
  "matchType": "ODI" | "TEST" | "T20" // optional
  "isLocked": boolean // optional
}

delete

Deletes a chat room. Rate Limit: 10 requests per minute per user Authorization: Must be room creator Request Body:
{
  "action": "delete",
  "roomId": "string (required)"
}

Client Usage

From services/rooms.service.ts:
export async function executeRoom({
  action,
  roomId,
  teams,
  startTime,
  endTime,
  matchType,
  isLocked,
}: {
  action: "create" | "update" | "delete";
  teams: string[];
  startTime: string;
  matchType: "ODI" | "TEST" | "T20";
  isLocked: boolean;
  roomId?: string;
  endTime?: string;
}) {
  const execution = await functions.createExecution({
    functionId: ROOMS_GUARD_FUNCTION_ID,
    body: JSON.stringify({
      action,
      roomId,
      teams,
      startTime,
      endTime,
      matchType,
      isLocked,
    }),
    async: false,
  });

  if (execution.status === "failed") {
    throw new Error(execution.errors);
  }

  return execution;
}

room-message-guard

Function ID: 695e8e91000d6c2bde1f

Purpose

Handles real-time chat messages in live cricket match rooms with push notifications.

Actions

create

Sends a message in a live room. Rate Limit: 60 requests per minute per user Validation:
  • Room must have status “live” (between start and end time)
  • User must be authenticated
Request Body:
{
  "action": "create",
  "roomId": "string (required)",
  "content": "string (required)"
}
Side Effects:
  1. Creates message with isEdited: false
  2. Increments user’s messageCount in users table
  3. Sends push notification to room creator (if not the sender)
  4. Stores notification in notifications table
Push Notification Format:
{
  sound: 'default',
  title: 'New message',
  body: `You have a new message in your ${team1} vs ${team2} room. Tap to check.`,
  to: '<push_token>'
}
Permissions Set:
  • Read: All authenticated users
  • Update: Message author only
  • Delete: Message author only

update

Edits an existing message. Rate Limit: 60 requests per minute per user Authorization: Must be room owner Validation:
  • Room must be “live”
Request Body:
{
  "action": "update",
  "roomId": "string (required)",
  "roomMessageId": "string (required)",
  "content": "string (required)"
}

delete

Deletes a message from the room. Rate Limit: 60 requests per minute per user Authorization: Must be room owner Validation:
  • Room must be “live”
Request Body:
{
  "action": "delete",
  "roomId": "string (required)",
  "roomMessageId": "string (required)"
}

notifications-guard

Function ID: 6970a9c400040f0cb857

Purpose

Retrieves user-specific notifications with no rate limiting.

Actions

fetchByUserId

Fetches all notifications for the authenticated user. Rate Limit: None Request Body:
{
  "action": "fetchByUserId"
}
Response:
{
  "success": true,
  "data": {
    "rows": [
      {
        "$id": "string",
        "userId": "string",
        "title": "string",
        "content": "string",
        "$createdAt": "ISO 8601 datetime"
      }
    ]
  }
}
Behavior:
  • Automatically filters by authenticated user’s ID
  • Results ordered by creation date (newest first)

leaderboard-guard

Function ID: 6963302c0021374fee8d

Purpose

Retrieves top 10 users ranked by message count.

Execution

Rate Limit: None Authentication: Required but no user-specific filtering Request: No action parameter needed Response:
{
  "rows": [
    {
      "$id": "userId",
      "username": "string",
      "messageCount": number,
      "pushTokens": ["string"]
    }
  ]
}
Query:
  • Orders by messageCount descending
  • Limits to 10 results

user-push-token-guard

Function ID: 69704fe8001df0792591

Purpose

Manages push notification tokens for mobile devices.

Actions

send

Adds or updates a push token for the authenticated user. Rate Limit: None Request Body:
{
  "action": "send",
  "pushToken": "string (required)"
}
Behavior:
  • Uses upsertRow to create or update user entry
  • Avoids duplicate tokens (checks before adding)
  • Maintains array of all user’s device tokens

delete

Removes a specific push token. Rate Limit: None Request Body:
{
  "action": "delete",
  "pushToken": "string (required)"
}
Behavior:
  • Filters out the specified token from user’s pushTokens array
  • Preserves other tokens

Error Handling

All guard functions throw errors in these scenarios:

Unauthorized (401)

if (!userId) {
  throw new Error('Unauthorized: User is not authorized');
}

Forbidden (403)

if (post.authorId !== userId) {
  throw new Error("Forbidden: You aren't author of this post");
}

Rate Limit Exceeded (429)

if (row.activityCount >= 10) {
  throw new Error(
    'Rate limit exceeded: Too many requests in a short period.'
  );
}

Validation Errors (400)

if (typeof content !== 'string' || !content.trim()) {
  throw new Error("Error: Comment can't be empty");
}

Generic Error Response

try {
  // ... function logic
} catch (error) {
  throw new Error(`Unable to process query ${error}`);
}

Authentication

All guard functions require:
  1. User ID Header: x-appwrite-user-id from Appwrite session
  2. API Key Header: x-appwrite-key for server-side permissions
const userId = req.headers['x-appwrite-user-id'];
const client = new Client()
  .setEndpoint(process.env.APPWRITE_ENDPOINT)
  .setProject(process.env.APPWRITE_PROJECT_ID)
  .setKey(req.headers['x-appwrite-key']);

Best Practices

Synchronous Execution

Always use async: false for guard functions to ensure operations complete before returning to the client:
const execution = await functions.createExecution({
  functionId: GUARD_FUNCTION_ID,
  body: JSON.stringify({ action, ...params }),
  async: false, // Wait for completion
});

Error Checking

Always check execution status:
if (execution.status === "failed") {
  throw new Error(`Execution failed: ${execution.errors}`);
}

Rate Limit Awareness

Design UI to prevent users from hitting rate limits:
  • Disable submit buttons temporarily after actions
  • Show cooldown timers for high-frequency actions
  • Batch operations when possible

Permission Design

Guard functions set row-level permissions during creation:
permissions: [
  Permission.read(Role.users()),      // All authenticated users can read
  Permission.update(Role.user(userId)), // Only author can update
  Permission.delete(Role.user(userId))  // Only author can delete
]
This ensures database-level security even if guard functions are bypassed.

Build docs developers (and LLMs) love