I've been trying to get the Appwrite API to work in a Function, but I just can't seem to get it.
The function runs fine when I use just the Stripe API code, but once I start adding the Appwrite API to save some user preferences, it returns Execution timed out.
.
What I checked:
- I'm instantiating a new Appwrite client
- I'm setting
endPoint
,setProject
,setJWT
to the Appwrite client - I'm creating the Appwrite
Account
client - The Function initially had
Users
execute access, but I triedAny
, to no avail
When I log the client
{
"client": {
"endpoint": "https://backend.*****.com/v1",
"headers": {
"accept-encoding": "*",
"content-type": "",
"x-sdk-name": "Node.js",
"x-sdk-platform": "server",
"x-sdk-language": "nodejs",
"x-sdk-version": "11.0.0",
"X-Appwrite-Response-Format": "1.4.0",
"x-appwrite-project": "******",
"x-appwrite-jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1*************"
},
"selfSigned": false
}
}
My /src/lib/appwrite.ts:
constructor(
APPWRITE_ENDPOINT: string,
APPWRITE_PROJECT_ID: string,
APPWRITE_USER_JWT: string,
) {
this.client = new Client();
this.account = new Account(this.client);
this.client
.setEndpoint(APPWRITE_ENDPOINT). // These are logging fine
.setProject(APPWRITE_PROJECT_ID) // These are logging fine
.setJWT(APPWRITE_USER_JWT); // These are logging fine
}
async getPreferences(
functionContext: AppwriteFunctionContext,
): Promise<Record<string, string>> {
try {
return await this.account.getPrefs();
} catch (err) {
functionContext.error(err); // It never seems to catch
}
return {};
}
My main.ts:
const userPreferences = await appwriteService.getPreferences(functionContext);
Any hint on what I'm doing wrong would be appreciated.
Some findings:
- I'm using a custom domain. When I set the
endpoint
in the function to the non-custom domain (e.g. https://appwrite.xxxxxx.com, it's contining the function instead of just returning nothing - In above code I'm using the
Account
SDK with aJWT
, I've switched to theUsers
SDK with a global API key
More notes:
- Appwrite 1.4.3
- Self hosted on Digital Ocean
- I'm triggering this function from a Next.js frontend with
createExecution
- The API key that I'm using never shows that it's accessed in the Appwrite dashboard, which is weird, because in the Function I initialise specifically with that key
Error: connect ECONNREFUSED 127.0.1.1:443
at AppwriteService.getPreferences (file:///usr/local/server/src/function/dist/lib/appwrite.js:22:19)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Module.default (file:///usr/local/server/src/function/dist/main.js:28:29)
at async execute (/usr/local/server/src/server.js:141:22)
at async /usr/local/server/src/server.js:158:13
When I get ECONNREFUSED
, is that a server config error, or does it mean my credentials are invalid?
connect ECONNREFUSED 127.0.1.1:443 I faced this exact issue, when I setup Appwrite on something other than 443.
Since you're self-hosting, can you tell me what your traefik is set up like? Specifically the traefik service:
services:
traefik:
image: traefik:2.7
container_name: appwrite-traefik
<<: *x-logging
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
restart: unless-stopped
ports:
- 5080:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-config:/storage/config:ro
- appwrite-certificates:/storage/certificates:ro
depends_on:
- appwrite
networks:
- gateway
- appwrite
As you can see, traefik is set up to map 443 to 443.
If it helps, I am using Nginx Proxy Manager in front of Appwrite as a load balancer.
Do you know why the IP is 127.0.1.1?
ooh yeah that's weird
@safwan I came across the question you posted before, but figured it must be something else since I'm using the default ports.
This is what my Traefik service looks like:
traefik:
image: traefik:2.9
container_name: appwrite-traefik
<<: *x-logging
command:
- --providers.file.directory=/storage/config
- --providers.file.watch=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
- --entrypoints.appwrite_web.address=:80
- --entrypoints.appwrite_websecure.address=:443
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-config:/storage/config:ro
- appwrite-certificates:/storage/certificates:ro
depends_on:
- appwrite
networks:
- gateway
- appwrite
Maybe a bit more information regarding my setup will help.
@Steven I have no idea why the IP is 127.0.1.1. Can I debug or log something that gives an answer to that question?
My setup:
- I'm running Appwrite on a subdomain:
https://appwrite.xxxxx.com
- I've added a custom domain for the web to interact with:
https://backend.xxxxx.com
- The Function variables:
- setEndpoint:
https://appwrite.xxxxx.com/v1
(I tried the custom domainhttps://backend.xxxxx.com
too) - setProject:
xxxxx
(confirmed in Appwrite) - setKey:
xxxxx
(new generated API key with all access)
- setEndpoint:
- The Function is deployed manually with Appwrite CLI. Build etc. is fine:
- execute:
users
- I've rewrote it to just use updatePrefs and then getPrefs and then log it in the console.
- execute:
This is the updatePreferences function:
async updatePreferences(
functionContext: AppwriteFunctionContext,
APPWRITE_USER_ID: string,
userPreferences: Record<string, string>,
) {
try {
functionContext.log("updatePreferences");
functionContext.log(userPreferences);
functionContext.log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
return await this.users.updatePrefs(APPWRITE_USER_ID, userPreferences);
} catch (err: any) {
functionContext.log("updatePreferences error");
functionContext.log(err.message);
throw new Error(err.message);
}
}
I call it like so:
const updatedUserPreferences = await appwriteService.updatePreferences(
functionContext,
APPWRITE_USER_ID,
{ TestKey: "TestValue" },
);
It always throws: connect ECONNREFUSED 127.0.1.1:443
Relevant parts of my .env
:
_APP_ENV=production
_APP_LOCALE=en
_APP_OPTIONS_ABUSE=enabled
_APP_OPTIONS_FORCE_HTTPS=enabled
_APP_OPENSSL_KEY_V1=xxxxx
_APP_DOMAIN=appwrite.xxxxx.com
_APP_DOMAIN_FUNCTIONS=
_APP_DOMAIN_TARGET=appwrite.xxxxx.com
_APP_INFLUXDB_HOST=influxdb
_APP_INFLUXDB_PORT=8086
_APP_STATSD_HOST=telegraf
_APP_STATSD_PORT=8125
_APP_SMTP_HOST=mailpit
_APP_SMTP_PORT=1025
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CONTAINERS=10
_APP_FUNCTIONS_CPUS=0
_APP_FUNCTIONS_MEMORY=0
_APP_FUNCTIONS_MEMORY_SWAP=0
_APP_FUNCTIONS_RUNTIMES=node-20.0
_APP_EXECUTOR_SECRET=xxxxx
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
_APP_FUNCTIONS_INACTIVE_THRESHOLD=300
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600
Might've found something:
- When I run
docker exec appwrite vars
, the_APP_FUNCTIONS_RUNTIMES_NETWORK
value is empty - When I SSH to the server and check the
.env
file, the value isruntimes
- When I run
docker compose up -d
, the env vars don't seem to reload / update
I guess my Appwrite install is corrupt. I installed 1.4.2 and followed the docs to update to 1.4.3, then ran the migration. I'll try a 1.4.3 clean install.
Also strange:
cat .env | grep RUNTIME
gives me:
_APP_EXECUTOR_RUNTIME_NETWORK="runtimes"
_APP_FUNCTIONS_RUNTIMES="node-20.0"
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
docker exec appwrite vars | grep RUNTIME
gives me:
- _APP_FUNCTIONS_RUNTIMES=node-20.0
- _APP_EXECUTOR_RUNTIME_NETWORK=
- OPEN_RUNTIMES_NETWORK=
- _APP_FUNCTIONS_RUNTIMES_NETWORK=
Is this expected or is there some sort of .env
cache? This is after changing the .env file and running docker compose up -d
.
This might all go back to running the migration from 1.4.2 to 1.4.3. and the migration script breaking on the GitHub integration SSH key _APP_VCS_GITHUB_PRIVATE_KEY
, because it's multiline.
I've manually updated the .env file after the migration from 1.4.2 to 1.4.3, but it seems something broke there.
Allright. I must be doing something simple very wrong, it's still not working** after a fresh 1.4.3 install.**
I don't get connect ECONNREFUSED 127.0.1.1:443
anymore, but the function times seems to stop running and times out right after I call any of the Appwrite functions, specifically updatePrefs
.
Any idea on how to debug this?
docker logs appwrite-worker-functions
gives me:
Fetched 1 functions...
[Job] (65114aa9c68d01.13467678) successfully run.
docker logs appwrite-executor-********-65114879c1f66e6288ad
gives me:
pm2 launched in no-daemon mode (you can add DEBUG="*" env variable to get more messages)
2023-09-25T08:45:53: PM2 log: Launching in no daemon mode
2023-09-25T08:45:54: PM2 log: [PM2] Starting /usr/local/server/src/server.js in fork_mode (1 instance)
2023-09-25T08:45:54: PM2 log: App [server:0] starting in -fork mode-
2023-09-25T08:45:54: PM2 log: App [server:0] online
2023-09-25T08:45:54: PM2 log: [PM2] Done.
docker logs appwrite
gives me:
Nothing special.
cat .env | grep RUNTIME
gives me:
_APP_FUNCTIONS_RUNTIMES=node-20.0
_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
docker exec appwrite vars | grep RUNTIME
gives me:
- _APP_FUNCTIONS_RUNTIMES=node-20.0
- _APP_EXECUTOR_RUNTIME_NETWORK=
- OPEN_RUNTIMES_NETWORK=
- _APP_FUNCTIONS_RUNTIMES_NETWORK=
The function code (node-20):
import { getEnvironmentVariablesOrThrow, getHeadersOrThrow, } from "./utils/index.js";
import AppwriteService from "./lib/appwrite.js";
export default async (functionContext) => {
const { req, res, log } = functionContext;
const { APPWRITE_USER_ID } = getHeadersOrThrow(functionContext, req.headers, [
"x-appwrite-user-id",
]);
const { APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, APPWRITE_API_KEY } = getEnvironmentVariablesOrThrow(functionContext, process.env, [
"APPWRITE_ENDPOINT",
"APPWRITE_PROJECT_ID",
"APPWRITE_API_KEY",
]);
log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
log(`APPWRITE_ENDPOINT: ${APPWRITE_ENDPOINT}`);
log(`APPWRITE_PROJECT_ID: ${APPWRITE_PROJECT_ID}`);
log(`APPWRITE_API_KEY: ${APPWRITE_API_KEY}`);
const appwriteService = new AppwriteService(APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, APPWRITE_API_KEY);
const updatedUserPreferences = await appwriteService.updatePreferences(functionContext, APPWRITE_USER_ID, { TestKey: "TestValue" });
log(updatedUserPreferences);
const userPreferences = await appwriteService.getPreferences(functionContext, APPWRITE_USER_ID);
log(userPreferences);
return res.json({
status: "success",
}, 200);
};
The appwrite service code in the function
import { Client, Users } from "node-appwrite";
class AppwriteService {
client;
users;
constructor(APPWRITE_ENDPOINT, APPWRITE_PROJECT_ID, APPWRITE_API_KEY) {
this.client = new Client();
this.client
.setEndpoint(APPWRITE_ENDPOINT)
.setProject(APPWRITE_PROJECT_ID)
.setKey(APPWRITE_API_KEY);
this.users = new Users(this.client);
}
async getPreferences(functionContext, APPWRITE_USER_ID) {
try {
functionContext.log("getPreferences");
functionContext.log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
return await this.users.getPrefs(APPWRITE_USER_ID);
}
catch (err) {
functionContext.log("getPreferences error");
functionContext.log(err.message);
throw new Error(err.message);
}
}
async updatePreferences(functionContext, APPWRITE_USER_ID, userPreferences) {
try {
functionContext.log("updatePreferences");
functionContext.log(userPreferences);
functionContext.log(`APPWRITE_USER_ID: ${APPWRITE_USER_ID}`);
return await this.users.updatePrefs(APPWRITE_USER_ID, userPreferences);
}
catch (err) {
functionContext.log("updatePreferences error");
functionContext.log(err.message);
throw new Error(err.message);
}
}
}
export default AppwriteService;
what is APPWRITE_ENDPOINT
now and where is your appwrite instance installed?
APPWRITE_ENDPOINT
is https://appwrite.xxxxx.com/v1
My Appwrite instance is installed on a Digital Ocean droplet and publicly accessible. Path is /root/appwrite
. I can share the domain / IP if you'd like to.
I created a new droplet yesterday with a Docker image pre-installed (link), then ran the Unix install command for version 1.4.3, found in the Appwrite Self Hosted docs
Side note, I'm triggering this function from a localhost:3000 next.js app with executeFunction
after I created a magicUrl account and logging in.
The Appwrite client in the client side connects to the configured custom domain (https://backend.xxxxx.com)
Use case for this Function and accessing the Appwrite user preferences is:
- Get all the user preferences in the Function
- If there is not a Stripe ID saved in the Appwrite user preferences, call the Stripe API without an ID and let Stripe create one
- If there is a Stripe ID saved in the Appwrite user preferences, call another Stripe API with the ID that we saved previously
Good lord, it's solved. The function and Appwrite install was fine.
I checked out the Digital Ocean one-click setup for Appwrite repository and there's a script that adds port 80 and 443 to the UFW rules.
The firewall blocked my Function from accessing Appwrite without throwing.
For anyone going through the same and doesn't want to have gray hair:
Check your firewall rules in the droplet:
ufw status
If it doesn't show 443 and port 80 like this:
22/tcp LIMIT Anywhere
2375/tcp ALLOW Anywhere
2376/tcp ALLOW Anywhere
22/tcp (v6) LIMIT Anywhere (v6)
2375/tcp (v6) ALLOW Anywhere (v6)
2376/tcp (v6) ALLOW Anywhere (v6)
Update the firewall rules by running:
ufw allow ssh
ufw allow 80
ufw allow 443
ufw --force enable
Double check if the rules are active now:
22/tcp ALLOW Anywhere
2375/tcp ALLOW Anywhere
2376/tcp ALLOW Anywhere
80 ALLOW Anywhere
443 ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
2375/tcp (v6) ALLOW Anywhere (v6)
2376/tcp (v6) ALLOW Anywhere (v6)
80 (v6) ALLOW Anywhere (v6)
443 (v6) ALLOW Anywhere (v6)
[SOLVED] Function always returns "Execution timed out." on self-hosted Appwrite
Hindsight: I wanted to install a fresh 1.4.3, that's why I didn't use the one-click Appwrite on Digital Ocean. But I see it's now always using the latest version.
If there is not a Stripe ID saved in the Appwrite user preferences, call the Stripe API without an ID and let Stripe create one
btw...i'd be wary of saving the stripe ID in user preferences because the user is freely able to update user preferences 👀
Thanks Steven, good tip.
Recommended threads
- Get team fail in appwrite function
I try to get team of a user inside appwrite function, but i get this error: `AppwriteException: User (role: guests) missing scope (teams.read)` If i try on cl...
- Deploy function not working - 503
Hellon i get this error message, when i try to deploy a new version of a function <html><body><h1>503 Service Unavailable</h1>No server is available to handle...
- Error When load the website
Hi, I am getting this error whenever I reload my website please help me, I am using react Error: ** GET https://cloud.appwrite.io/v1/account 401 (Unauthoriz...