Appwrite Functions offer a familiar interface if you've developed REST endpoints. Each function is handled following a request and response pattern.
Lifecycle
There is a clear lifecycle for all Appwrite Functions, from beginning to end. Here's everything that happens during a function execution.
The function is invoked.
Appwrite passes in request information like headers, body or path through the context.req object.
The runtime executes the code you defined, you can log through the context.log() or context.error() methods.
Function terminates when you return results using return context.res.send(), return context.res.json() or similar.
You'll find all of these steps in a simple function like this.
import { Client } from 'node-appwrite';
// This is your Appwrite function
// It's executed each time we get a request
export default async ({ req, res, log, error }) => {
// Why not try the Appwrite SDK?
//
// const client = new Client()
// .setEndpoint('https://cloud.appwrite.io/v1')
// .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
// .setKey(process.env.APPWRITE_API_KEY);
// You can log messages to the console
log('Hello, Logs!');
// If something goes wrong, log an error
error('Hello, Errors!');
// The `req` object contains the request data
if (req.method === 'GET') {
// Send a response with the res object helpers
// `res.send()` dispatches a string back to the client
return res.send('Hello, World!');
}
// `res.json()` is a handy helper for sending JSON
return res.json({
motto: 'Build Fast. Scale Big. All in One Place.',
learn: 'https://appwrite.io/docs',
connect: 'https://appwrite.io/discord',
getInspired: 'https://builtwith.appwrite.io',
});
};
If you prefer to learn through more examples like this, explore the examples page.
Context object
Context is an object passed into every function to handle communication to both the end users, and logging to the Appwrite Console. All input, output, and logging must be handled through the context object passed in.
You'll find these properties in the context object.
Property | Description |
req | Contains request information like method, body, and headers. See full examples in the request section. |
res | Contains methods to build a response and return information. See full examples in the response section. |
log() | Method to log information to the Appwrite Console, end users will not be able to see these logs. See full examples in the logging section. |
error() | Methoc to log errors to the Appwrite Console, end users will not be able to see these errors. See full examples in the logging section. |
Destructuring assignment
Some languages, namely JavaScript, support destructuring. You'll see us use destructuring in examples, which has the following syntax.
Learn more about destructuring assignment.
// before destructuring
export default async function (context) {
context.log("This is a log!");
return context.res.send("This is a response!");
}
// after destructuring
export default async function ({ req, res, log, error }) {
log("This is a log!");
return res.send("This is a response!");
}
Request
If you pass data into an Appwrite Function, it'll be found in the request object. This includes all invocation inputs from Appwrite SDKs, HTTP calls, Appwrite events, or browsers visiting the configured domain. Explore the request object with the following function, which logs all request params to the Appwrite Console.
export default async ({ req, res, log }) => {
log(req.bodyRaw); // Raw request body, contains request data
log(JSON.stringify(req.body)); // Object from parsed JSON request body, otherwise string
log(JSON.stringify(req.headers)); // String key-value pairs of all request headers, keys are lowercase
log(req.scheme); // Value of the x-forwarded-proto header, usually http or https
log(req.method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.
log(req.url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50
log(req.host); // Hostname from the host header, such as awesome.appwrite.io
log(req.port); // Port from the host header, for example 8000
log(req.path); // Path part of URL, for example /v1/hooks
log(req.queryString); // Raw query params string. For example "limit=12&offset=50"
log(JSON.stringify(req.query)); // Parsed query params. For example, req.query.limit
return res.send("All the request parameters are logged to the Appwrite Console.");
};
Headers
Appwrite Functions will always receive a set of headers that provide meta data about the function execution. These are provided alongside any custom headers sent to the function.
Variable | Description |
x-appwrite-trigger | Describes how the function execution was invoked. Possible values are http, schedule or event. |
x-appwrite-event | If the function execution was triggered by an event, describes the triggering event. |
x-appwrite-user-id | If the function execution was invoked by an authenticated user, display the user ID. This doesn't apply to Appwrite Console users or API keys. |
x-appwrite-user-jwt | JWT token generated from the invoking user's session. Used to authenticate Server SDKs to respect access permissions. Learn more about JWT tokens. |
x-appwrite-country-code | Displays the country code of the configured locale. |
x-appwrite-continent-code | Displays the continent code of the configured locale. |
x-appwrite-continent-eu | Describes if the configured local is within the EU. |
Response
If you need to send a response to the invoker of the function, such as a user, client app, or an integration, use the response object. The response information will not be logged to the Appwrite Console. There are several possible ways to send a response, explore them in the following Appwrite Function.
export default async ({ req, res, log }) => {
switch (req.query.type) {
case 'empty':
return res.empty();
case 'json':
return res.json({"type": "This is a JSON response"});
case 'redirect':
return res.redirect("https://appwrite.io", 301);
case 'html':
return res.send(
"<h1>This is an HTML response</h1>", 200, {
"content-type": "text/html"
});
default:
return res.send("This is a text response");
}
}
To get the different response types, set one of the following query parameters in the generated domain of your function.
Type | Query Param | Example |
text | /?type=text | https://64d4d22db370ae41a32e.appwrite.global/?type=text |
json | /?type=json | https://64d4d22db370ae41a32e.appwrite.global/?type=json |
redirect | /?type=redirect | https://64d4d22db370ae41a32e.appwrite.global/?type=redirect |
html | /?type=html | https://64d4d22db370ae41a32e.appwrite.global/?type=html |
empty | / | https://64d4d22db370ae41a32e.appwrite.global/ |
Logging
To protect user privacy, the request and response objects are not logged to the Appwrite Console by default. This means, to see logs or debug function executions you need to use the log() and error() methods. These logs are only visible to developers with access to the Appwrite Console.
Here's an example of using logs and errors.
export default async ({ req, res, log, error }) => {
log("This is a log, use for logging information to console");
log(`This function was called with ${req.method} method`);
error("This is an error, use for logging errors to console");
return res.send("Check the Appwrite Console to see logs and errors!");
};
You can access these logs through the following steps.
In Appwrite Console, navigate to Functions.
Click to open a function you wish to inspect.
Under the Executions tab, click on an execution.
In the Response section, you'll be able to view logs under the Logs and Errors tabs.
Accessing environment variables
If you need to pass constants or secrets to Appwrite Functions, you can use environment variables. Environmental variables can be global, or function-specific.
Appwrite API keys
If your function is using an Appwrite SDK with an API key, this API key needs to be generated and passed in manually. API keys are not passed by default for security reasons.
Variable | Description |
APPWRITE_FUNCTION_ID | The ID of the running function. |
APPWRITE_FUNCTION_NAME | The Name of the running function. |
APPWRITE_FUNCTION_DEPLOYMENT | The deployment ID of the running function. |
APPWRITE_FUNCTION_PROJECT_ID | The project ID of the running function. |
APPWRITE_FUNCTION_RUNTIME_NAME | The runtime of the running function. |
APPWRITE_FUNCTION_RUNTIME_VERSION | The runtime version of the running function. |
Function-level environment variables
Function-level environment variables will only be accessible in the function they belong to. Function-level environment variables will override project-level variables when they have conflicting names.
In Appwrite Console, navigate to Functions.
Click to open a function you wish to add variables to.
Under the Settings tab, navigate to Environment variables.
Create an environment variable by clicking Create variable, using the Editor, or import new variables through a .env file.
Project-level variables
Project-level variables are accessible to all Appwrite Functions in your project. Function-level environment variables will override project-level variables when they have conflicting names.
In the Appwrite Console, navigate to your project's Settings page. Navigate to Global variables section. Create an environment variable by clicking Create variable, using the Editor, or import new variables through a .env file.
You can access the environment variables through the systems library of each language.
export default async ({ req, res, log }) => {
return res.send(process.env.MY_VAR);
}
Dependencies
Your function's dependencies should be managed by the package manager of each language. By default, we include the following package managers in each runtime.
Language | Package Manager | Commands | |
Node.js | NPM | npm install | |
PHP | Composer | composer install | |
Python | pip | pip install -r requirements.txt | |
Ruby | Bundler | bundle install | |
Deno | deno | deno cache <ENTRYPOINT_FILE> | |
Dart | pub | pub get | |
Swift | Swift Package Manager | swift package resolve | |
.NET | NuGet | dotnet restore | |
Kotlin | Gradle | N/A | |
Java | Gradle | N/A | |
C++ | None | N/A |
To install your dependencies before your function is built, you should add the relevant install command to the top your function's Build setting > Commands.
Using Appwrite in a function
Appwrite can be used in your functions by adding the relevant SDK to your function's dependencies. Authenticating with Appwrite is done via an API key or a JWT token. API keys must be generated and exported as an environment variable.
You can read more about authentication in the JWT login section of the docs.
Using with API key
API keys have defined scopes when you create them. They ignore permissions and operate without a sessions. Use API keys if the function should act as an admin type role, instead of acting on behalf of a user. Pass in your API key as an environment variable. Never share API keys with users.
import { Client, Databases, ID } from 'node-appwrite';
export default async ({ req, res, log, error }) => {
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1')
.setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
.setKey(process.env.APPWRITE_API_KEY);
const databases = new Databases(client);
try {
await databases.createDocument(
'[DATABASE_ID]',
'[COLLECTION_ID]',
ID.unique(),
{}
)
} catch (e) {
error("Failed to create document: " + e.message)
return res.send("Failed to create document")
}
return res.send("Document created")
}
Using with JWT
JWTs allow you to act on behalf of an user in your Appwrite Function. When using JWTs, you will be able to access and change only the resources with the same permissions as the user account that signed the JWT. This preserves the permissions you configured on each resource.
If the Appwrite Function is invoked by an authenticated user, the x-appwrite-user-jwt header is automatically passed in.
import { Client, Databases, ID } from 'node-appwrite';
export default async ({ req, res, log }) => {
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1')
.setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
if (req.headers['x-appwrite-user-jwt']) {
client.setJWT(req.headers['x-appwrite-user-jwt'])
} else {
return res.send("Please sign in, JWT not found")
}
const databases = new Databases(client);
try {
await databases.createDocument(
'[DATABASE_ID]',
'[COLLECTION_ID]',
ID.unique(),
{}
)
} catch (e) {
log("Failed to create document: " + e.message)
return res.send("Failed to create document")
}
return res.send("Document created")
}
Code splitting
As your functions grow, you may find yourself needing to split your code into multiple files. This helps you keep your codebase maintainable and easy to read. Here's how you can accomplish code splitting.
// src/utils.js
export function add(a, b) {
return a + b;
}
// src/main.js
import { add } from './utils.js';
export default function ({ res }) {
return res.send(add(1, 2));
}
<?php
// src/utils.php
function add($a, $b) {
return $a + $b;
}
<?php
// src/main.php
require_once(__DIR__ . '/utils.php');
return function ($context) {
return $context->res->send(add(1, 2));
};
# src/utils.py
def add(a, b):
return a + b
# src/main.py
from .utils import add
def main(context):
return context.res.send(add(1, 2))
# lib/utils.rb
def add(a, b)
return a + b
end
# lib/main.rb
require_relative 'utils'
def main(context)
return context.res.send(add(1, 2))
end
// src/utils.ts
export function add(a: number, b: number): number {
return a + b;
}
// src/main.ts
import { add } from './utils.ts';
export default function ({res}: {res: any}) {
return res.send(add(1, 2));
}
// lib/utils.dart
int add(int a, int b) {
return a + b;
}
// lib/main.dart
import 'dart:async';
import 'package:package_name/utils.dart';
Future<dynamic> main(final context) async {
return context.res.send(add(1, 2));
}
// Sources/utils.swift
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
// Sources/index.swift
import Foundation
func main(context: RuntimeContext) async throws -> RuntimeOutput {
return context.res.send(add(1, 2))
}
// src/Utils.cs
namespace DotNetRuntime
{
public static class Utils
{
public static int Add(int a, int b)
{
return a + b;
}
}
}
// src/Index.cs
namespace DotNetRuntime
{
public class Handler {
public async Task<RuntimeOutput> Main(RuntimeContext Context)
{
return Context.Res.Send(Utils.Add(1, 2));
}
}
}
// src/Utils.kt
package io.openruntimes.kotlin.src
object Utils {
fun add(a: Int, b: Int): Int {
return a + b
}
}
// src/Main.kt
package io.openruntimes.kotlin.src
import io.openruntimes.kotlin.RuntimeContext
import io.openruntimes.kotlin.RuntimeOutput
import io.openruntimes.kotlin.Utils
class Main {
fun main(context: RuntimeContext): RuntimeOutput {
return context.res.send(Utils.add(1, 2))
}
}
// src/Utils.java
package io.openruntimes.java.src;
class Utils {
public static int add(int a, int b) {
return a + b;
}
}
package io.openruntimes.java.src;
import io.openruntimes.java.RuntimeContext;
import io.openruntimes.java.RuntimeOutput;
import io.openruntimes.java.Utils;
public class Main {
public RuntimeOutput main(RuntimeContext context) throws Exception {
return context.res.send(Utils.add(1, 2));
}
}
Upgrade
Appwrite Functions received major updates in Appwrite version 1.4. If you still have functions from previous versions, they will be read-only in Appwrite 1.4. You will have to migrate your old functions to follow new runtime syntax.
Here's a checklist of things you need to know.
The parameter passed into functions has changed. req and res has been replaced by context, which contains new logger methods. Learn about context.
To improve privacy and logging reliability, we provide new context.log() and context.error() functions. You can no longer use native logging methods. Learn about logging.
The old way of req.variables has been deprecated. You can now access variables passed into each function as environment variables. Learn about environment variables.
The req object has been updated to use terminology consistent with typical HTTP concepts. You'll now find familiar concepts like headers, body, HTTP methods, and others. Learn about request.
The response object has been updated. You can now specify headers, as well as use new methods like return redirects or empty responses. Learn about response.
Now, you must return a response such as return context.res.send(""). This prevents confusing errors when functions are terminated prematurely before a response is sent. Learn about response.
Some variables about how a function was triggered are now found in the context.req object as headers.