Using Appwrite Functions as a Server‑Side Command Boundary
Appwrite Functions can serve as a secure, authenticated command boundary. This guide walks through the minimal design, trust model, operational checks, failure modes, and when to move beyond a single synchronous function.
31 Aug 2025, 12:18 UTC

Problem Statement
Clients must be able to perform privileged Appwrite operations—such as creating a document in a tenant‑scoped collection—without exposing API keys or allowing the client to dictate which tenant to act on. The solution is to wrap the privileged call in an Appwrite Function that validates the caller’s membership, authorizes the operation, and performs the action with a server‑side API key.
Requirements
- Never ship Appwrite API keys to the browser or mobile app.
- Assume every function argument is untrusted: payload, tenant ID, resource ID, and even the authenticated user ID can be forged.
- Derive tenant context from the authenticated user’s membership state stored in Appwrite’s database or user attributes.
- Guarantee idempotent writes where possible to avoid duplicate side effects on retry.
- Use the Appwrite server SDK inside the function; the SDK must be initialized with an API key stored in the function’s environment configuration.
- Keep the function’s runtime bounded and synchronous; only use background workers if the operation can exceed the default function timeout or requires fan‑out.
Smallest Suitable Design
Deploy a single function per command. For example, a createInvoice command that accepts a tenant ID and invoice data. The function payload is a minimal JSON object:
{
"tenantId": "tenant_123",
"invoice": {
"amount": 150.00,
"currency": "USD",
"description": "Consulting services"
}
}
Inside the function (Node.js example):
require('dotenv').config();
const { Appwrite } = require("appwrite");
const client = new Appwrite();
client.setEndpoint(process.env.APPWRITE_ENDPOINT) // e.g. https://cloud.appwrite.io/v1
.setProject(process.env.APPWRITE_PROJECT)
.setKey(process.env.APPWRITE_FUNCTION_API_KEY);
const db = client.database();
module.exports = async function (req, res) {
const body = JSON.parse(req.body);
const { tenantId, invoice } = body;
// 1. Verify the caller is authenticated via Appwrite session
const session = req.headers["x-appwrite-session"];
if (!session) return res.status(401).json({ error: "unauthenticated" });
// 2. Resolve tenant membership server‑side
const userId = req.headers["x-appwrite-user-id"];
const membership = await db.getDocument("tenant_memberships", `${tenantId}_${userId}`);
if (!membership) return res.status(403).json({ error: "not a member of tenant" });
// 3. Perform privileged write
const result = await db.createDocument(tenantId, "invoices", {
$id: "unique()",
...invoice,
owner: userId
});
return res.json({ success: true, invoiceId: result.$id });
};
Deploy the function with the following command (run on a machine with the Appwrite CLI installed):
appwrite functions create \\
--name "Create Invoice" \\
--runtime "nodejs12" \\
--entrypoint "index.js" \\
--environment APPWRITE_FUNCTION_API_KEY=YOUR_API_KEY
Set the environment variable APPWRITE_FUNCTION_API_KEY in the function deployment UI or via the CLI. The key should have only the database.* scope and be rotated regularly.
Trust and Data Boundaries
- Client side – untrusted. The caller can send any tenant ID or resource ID.
- Payload – untrusted. Validate schema and types before use.
- User and tenant identifiers – derived from the authenticated session, not trusted from the request body.
- Function environment – trusted. The SDK key is stored securely and never exposed to the client.
The function must independently verify that the authenticated user belongs to the tenant before performing any tenant‑scoped operation.
Operational Checks
- Execution status – use
appwrite functions logs listto view success/failure and error stack traces. - Latency and timeout – monitor the
executionTimefield in logs; if it consistently approaches the function timeout (default 5 s), consider offloading or increasing limits. - Credential rotation – after rotating
APPWRITE_FUNCTION_API_KEY, verify that the function still succeeds. Failure indicates a missing or wrong scope. - Deployment health – ensure the function’s deployment status is
ready. Afaileddeployment should trigger alerts. - Retry handling – the function should be idempotent. If the external write cannot be repeated safely, return a unique request ID and let the client deduplicate.
Failure Modes and Mitigations
| Failure Mode | Impact | Mitigation |
|---|---|---|
| Duplicate execution | Idempotent writes may create duplicate records. | Generate unique IDs server‑side and reject duplicates via unique constraints. |
| Timeout after partial completion | Partial data written, inconsistent state. | Wrap writes in a transaction or use a compensating action. |
| Stale authorization data | Unauthorized access if membership cache is stale. | Always fetch membership from the database; avoid client‑side caching. |
| Malformed input | Runtime errors, stack traces in logs. | Validate JSON schema before processing. |
| Function unavailability | All privileged calls fail. | Deploy a redundant function instance; use health checks. |
| Broken deployment or credential rotation | Function cannot authenticate. | Automate deployment pipelines with secret management. |
When the Design Needs to Change
- Cross‑tenant administration – if a single function must act on multiple tenants, split the responsibilities into separate functions with tenant‑specific scopes.
- Secret‑bearing third‑party calls – move the call to a background worker or a dedicated function that runs with a tighter key.
- High request volume – scale the function horizontally or shift to a dedicated microservice to avoid contention on the Appwrite server SDK.
- Regulated audit requirements – add explicit audit logs with user, tenant, and operation details; consider a separate audit service.
- Multiple backend capabilities – if the command needs to coordinate across databases or message queues, orchestrate with a workflow engine rather than a single function.
Practical Verification Steps
- Deploy the function and invoke it with a valid authenticated session, ensuring the tenant ID matches the caller’s membership.
- Attempt the same call with a user from a different tenant; the function should return a 403 error.
- Send a malformed JSON payload; the function should return a 400 error and log the validation failure.
- Rotate the function’s API key and confirm that new invocations fail with a 500 error until the key is restored.
- Measure end‑to‑end latency for a typical payload; if it approaches the timeout, consider a background worker.
By following this architecture note, you can expose a secure, authenticated command boundary in Appwrite that protects sensitive operations, keeps API keys off the client, and provides clear operational visibility.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.