{"id":13534179,"url":"https://github.com/byawitz/appwrite-funcover","last_synced_at":"2025-04-01T22:31:17.130Z","repository":{"id":173514125,"uuid":"650869904","full_name":"byawitz/appwrite-funcover","owner":"byawitz","description":"Cover your Appwrite functions with a dedicated endpoint.","archived":true,"fork":false,"pushed_at":"2023-08-10T14:07:20.000Z","size":60,"stargazers_count":17,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-24T04:44:46.860Z","etag":null,"topics":["appwrite","appwrite-function","endpoint"],"latest_commit_sha":null,"homepage":"https://hub.docker.com/r/boolcode/appwrite-funcover","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/byawitz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-06-08T01:51:28.000Z","updated_at":"2025-03-04T12:26:33.000Z","dependencies_parsed_at":"2024-01-14T08:11:28.612Z","dependency_job_id":"96c7bbce-8b83-4cb0-8f54-7be9bd908a4e","html_url":"https://github.com/byawitz/appwrite-funcover","commit_stats":null,"previous_names":["boolcode/appwrite-funcover","byawitz/appwrite-funcover"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byawitz%2Fappwrite-funcover","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byawitz%2Fappwrite-funcover/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byawitz%2Fappwrite-funcover/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byawitz%2Fappwrite-funcover/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/byawitz","download_url":"https://codeload.github.com/byawitz/appwrite-funcover/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246720528,"owners_count":20822914,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appwrite","appwrite-function","endpoint"],"created_at":"2024-08-01T07:01:27.449Z","updated_at":"2025-04-01T22:31:16.852Z","avatar_url":"https://github.com/byawitz.png","language":"TypeScript","funding_links":[],"categories":["Tools"],"sub_categories":["Community-Built SDKs"],"readme":"# Appwrite Funcover\n\n\u003e Right before Appwrite's [releasing](https://github.com/appwrite/appwrite/discussions/5016) the next generation (4) of their functions, You can cover your Appwrite function with a dedicated endpoint!\n\nWith Funcover you can `cover` your function with any domain you want!\n\nThat means that you'll be able to access one of your [Appwrite functions](https://appwrite.io/docs/functions)  or all of them using any endpoint you want.\n\nThis feature will help you use Appwrite functions as a target-webhook, direct access URL (without the need to provide project id), And also simply, for your convince.\n\n## 💥 Funcover is built with:\n\n- [Bun](https://bun.sh/) _(Because I like it)_ - A fast all-in-one JavaScript runtime.\n- [Hono](https://hono.dev/) - Simple, ultrafast web framework.\n- TypeScript - I'm speechless :wink:\n\n### 📖 Table of Contents:\n\n- [Funcover Features](#-funcover-features)\n- [Installation](#-installation)\n    - [SSL](#-ssl)\n    - [Domain based](#-adding-funcover---domain-based)\n    - [Path based](#-adding-funcover---path-based)\n    - [A bit of explanation](#-deep-dive)\n- [Usages](#-usages)\n    - [Post Requests](#-note-for-post)\n    - [Logs](#-logs)\n    - [Global](#-global)\n    - [Multiple Instances](#-multiple-instances)\n    - [Rate limiting](#-rate-limiting--permissions)\n- [Environment variables](#-environment-variables)\n    - [API Keys](#api-key-variable-usages)\n    - [Flatten Headers](#flatten-headers-key-variable-usages)\n    - [Path as Data](#path-as-data-variable-usages)\n\n## 📃 Funcover features\n\n- [x] Access your Appwrite function through a GET request.\n- [x] User your Appwrite function as a Webhook.\n- [x] Passing all request headers.\n- [x] Passing all request Body/Form/Json data.\n- [x] Passing `data` query variable in GET requests.\n- [x] Can be used for single or all of your functions.\n- [x] Passing API Key.\n- [x] Access Funcover on custom path.\n\n## 🧑‍💻 Installation\n\nFuncover meant to be added to your current [self-hosted](https://appwrite.io/docs/self-hosting) Appwrite instance, and, this is what we going to cover in this page.\nHowever, you can use Funcover as a standalone Appwrite-Function proxy, and use some provider - Cloudflare, for example - to handle the SSL part for you.\n\n#### 🔒 SSL\n\n_If you're going to use Funcover in a Path Based mode you can skip this part._\n\nBefore adding Funcover you'll need make sure that the domain you're planning to use will have SSL, To do so we're harnessing Appwrite [custom-domain](https://appwrite.io/docs/custom-domains) feature.\n\nAfter adding your domain as custom-domain to any of your Appwrite project, and, the domain is now pointing to your Appwrite instance you can move to the next step.\n\n#### 🌐 Adding Funcover - Domain Based\n\nIn **Domain Based** mode we want to be able to access Funcover using a completely different domain.\n\nSSH into your server and edit your `docker-compose.yml` file.\n\nAt the bottom of the file right after the `telegraf` service, and, right before the `networks` section add the following.\n\n```yaml\n  funcover:\n    image: boolcode/appwrite-funcover:0.0.8\n    container_name: funcover\n    restart: unless-stopped\n    environment:\n      - ALLOW_GLOBAL=true\n      - DEFAULT_PROJECT=yourDefaultProjectID\n      - DEFAULT_FUNCTION=yourDefaultFunctionID\n    networks:\n      - appwrite\n      - gateway\n    labels:\n      - traefik.enable=true\n      - traefik.constraint-label-stack=appwrite\n      - traefik.docker.network=appwrite\n      - traefik.http.services.funcover.loadbalancer.server.port=3000\n      - traefik.http.routers.funcover-http.entrypoints=appwrite_web\n      - traefik.http.routers.funcover-http.rule=Host(`custom.domain.com`) \u0026\u0026 PathPrefix(`/`)\n      - traefik.http.routers.funcover-http.service=funcover\n      - traefik.http.routers.funcover-https.entrypoints=appwrite_websecure\n      - traefik.http.routers.funcover-https.rule=Host(`custom.domain.com`) \u0026\u0026 PathPrefix(`/`)\n      - traefik.http.routers.funcover-https.service=funcover\n      - traefik.http.routers.funcover-https.tls=true\n```\n\nReplace `custom.domain.com` with your newly attached custom-domain.\n\nLook [here](docker-compose.yml) for a complete example.\n\n#### ✍️ Adding Funcover - Path Based\n\nIn **Path Based** mode we want to be able to access Funcover with current Appwrite domain, But, with a custom path.\n\n_Added in version `0.0.6`_\n\n```yaml\n  funcover:\n    image: boolcode/appwrite-funcover:0.0.8\n    container_name: funcover\n    restart: unless-stopped\n    environment:\n      - ALLOW_GLOBAL=true\n      - DEFAULT_PROJECT=yourDefaultProjectID\n      - DEFAULT_FUNCTION=yourDefaultFunctionID\n    networks:\n      - appwrite\n      - gateway\n    labels:\n      - traefik.enable=true\n      - traefik.constraint-label-stack=appwrite\n      - traefik.docker.network=appwrite\n      - traefik.http.services.funcover.loadbalancer.server.port=3000\n      - traefik.http.routers.funcover-http.entrypoints=appwrite_web\n      - traefik.http.routers.funcover-http.rule=PathPrefix(`/v1/webhook`)\n      - traefik.http.routers.funcover-http.service=funcover\n      - traefik.http.routers.funcover-https.entrypoints=appwrite_websecure\n      - traefik.http.routers.funcover-https.rule=PathPrefix(`/v1/webhook`)\n      - traefik.http.routers.funcover-https.service=funcover\n      - traefik.http.routers.funcover-https.tls=true\n```\n\nKeep in mind that you'll need to add `PATH_INSTEAD_OF_DOMAIN` and `PATH_PREFIX` environment variables. check more [here](#-environment-variables).\n\n#### 🦉️ Deep dive\n\n\u003cdetails\u003e\n\u003csummary\u003eWhat is going on that snippet, what we just did??\u003c/summary\u003e\n\nWe have added a new service into the docker-compose.yml file, And, Here's a quick overview of the fields.\n\n- `image` - The name of the Docker image we are using for this service.\n- `container_name` - The name of the container. useful for logging and monitoring.\n- `restart` - Container restart policy. We have set it to `unless-stopped`, So, unless we're stopping it Docker will make sure the service is on.\n- `environment` - Here we're passing some values to be handled by Funcover at runtime. This is the best way to customize docker images without the need to rebuild them.\n- `networks` - Here we're connecting Funcover to `appwrite` network.\n- `labels` - Labels are piece of information that can be used by other containers,In this case the `traefik` one.\n\nDo notice the service rule (for http \u0026 https)\n\n```\n.rule=Host(`custom.domain.com`) \u0026\u0026 PathPrefix(`/`)\n```\n\nWe are setting two conditions for the rule.\n\n1. **Host** - We want to match the host to access Funcover.\n2. **PathPrefix** - Adding this part is important for the case we want Funcover to be able to parse all requests with no routes.\n\n_**Be aware** that when you're upgrading Appwrite this addition will be erased._\n\u003c/details\u003e\n\u003chr\u003e \n\nNow it's time to reload our Docker Compose environment by running,\n\n```shell\ndocker compose up -d\n```\n\n## 🛠️ Usages\n\nNow any time you'll access the custom-domain, your default function in your default project will run, And, Will return back the execution JSON. Just like you've used the [createExecution](https://appwrite.io/docs/client/functions?sdk=web-default#functionsCreateExecution) function.\n\n```json\n{\n  \"$id\"         : \"5e5ea5c16897e\",\n  \"$createdAt\"  : \"2020-10-15T06:38:00.000+00:00\",\n  \"$updatedAt\"  : \"2020-10-15T06:38:00.000+00:00\",\n  \"$permissions\": [\n    \"any\"\n  ],\n  \"functionId\"  : \"5e5ea6g16897e\",\n  \"trigger\"     : \"http\",\n  \"status\"      : \"processing\",\n  \"statusCode\"  : 0,\n  \"response\"    : \"\",\n  \"stdout\"      : \"\",\n  \"stderr\"      : \"\",\n  \"duration\"    : 0.4\n}\n```\n\nFor different return formats check the `RETURN_TYPE` variable [here](#-environment-variables).\n\nPassing data to the function can be done in any of the following four ways.\n\n1. GET `data` variable. `https://custom.domain.com/?data=data`\n2. GET parameter route as data. `https://custom.domain.com/data`\n3. POST using raw data with `application/json` content type.\n4. POST using form-data.\n5. POST using application/x-www-form-urlencoded.\n\n#### 🗒️ Note for POST\n\nFuncover will check first for raw JSON data before checking for `form-data` or `application/x-www-form-urlencoded`.\n\nAlso, You can use a filed named `rawData` to pass data directly to `data` key. Here's an example in JSON\n\n```json\n{\n  \"rawData\": \"I'm piece of data\"\n}\n```\n\nThis data will be sent to the function like so:\n\n```json\n{\n  \"data\": \"I'm piece of data\"\n}\n```\n\nAs in any other case, this one for example:\n\n```json\n{\n  \"data\": \"I'm piece of data\"\n}\n```\n\nThe data will be sent to the function completely, like so:\n\n```json\n{\n  \"data\": \"{\\\"data\\\":\\\"I'm piece of data\\\"}\"\n}\n```\n\n#### 📝 Logs\n\nFuncover don't produce any logs at runtime. In case you want to debug Funcover steps, or you just want to know more, You can pass the `VERBOSE` environment variable in the `docker-compose.yml` file.\n\nThen you'll be able to see the logs by running\n\n```shell\ndocker logs funcover\n```\n\nYou can add the `-f` flag to follow the log output.\n\n#### 🌎 Global\n\nFuncover can be used for a single function by setting the `DEFAULT_PROJECT` \u0026 `DEFAULT_FUNCTION` variables.\n\nAlso, Funcover can be used to handle all of your functions by project and function ID.\n\nTo do so you'll need to set the `ALLOW_GLOBAL` variable as `true` and reload your Docker Compose environment.\n\nNow you'll be able to access any of your functions by using the following route.\n\n```\nhttps://custom.domain.com/projectId/functionId/\n```\n\nAlso, here, You pass the data in GET or POST as in the default function endpoint.\n\n#### 🎶 Multiple instances.\n\nIn case you like to use Funcover on single mode, and/or you want to have multiple Funcover instances you can do so.\n\nIn the attached [example](docker-compose.yml) you can see how to set a second Funcover by looking on the `funcover-second` service.\n\n#### 🛑 Rate limiting \u0026 Permissions\n\nAs of now Funcover uses the [REST](https://appwrite.io/docs/rest) [Client-side](https://appwrite.io/docs/sdks#client) SDK. That mean that each function will hit their client rate-limit after 60 execution in a given minute.\n\nFor most use-cases that will more than enough.\n\nAlso, because Funcover execute the function through Client-side, Make sure you're adding the `Any` execution permission for your function permissions.\n\nIf you want your function to run as many times as you like you can add project API key with the `API_KEYS` environment variable.\n\n## 🏗️ Environment variables\n\n_You can take a look at [.env.example](.env.example) for possible values_\n\n| Variable                 | Usage                                                                                                                                                                                                                                                                                                                                                                  |\n|:-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `VERBOSE`                | When sets to `true` Funcover will produce more logs at runtime.                                                                                                                                                                                                                                                                                                        |\n| `ALLOW_GLOBAL`           | When sets to `true` Funcover will handle all of your function by project id.                                                                                                                                                                                                                                                                                           |\n| `RETURN_TYPE`            | How would you like to get the function output back \u003cbr/\u003e- `normal` - (Default) Just return the function output as JSON.\u003cbr/\u003e- `json` - Returns the `response` part from the function as parsed JSON.\u003cbr/\u003e- `html` - Returns the `response` part from the function as parsed HTML.\u003cbr/\u003e- `redirect` - Redirect the user the `response` returned URL.                    | \n| `ENDPOINT`               | Set as your Appwrite endpoint. \u003cbr\u003eFuncover will work even if you didn't provide any endpoint, As Funcover will access the main Appwrite container through Docker-network internal host url, `http://appwrite/v1`.                                                                                                                                                     |\n| `DEFAULT_PROJECT`        | Set as your default Appwrite project ID.                                                                                                                                                                                                                                                                                                                               |\n| `DEFAULT_FUNCTION`       | Set as your default Appwrite function ID.                                                                                                                                                                                                                                                                                                                              |\n| `PATH_INSTEAD_OF_DOMAIN` | When sets to `true` Funcover will add the value of `PATH_PREFIX` to all is routes.\u003cbr\u003eFor example, when `PATH_PREFIX` is sets = `v1/webhook` then Funcover will deliver the default function in `https://domain.com/v1/webhook` instead of `https://domain.com/` \u003cbr\u003eAll other option - like `ALLOW_GLOBAL` for example - are available to use when using this option. |\n| `PATH_PREFIX`            | Set the path to will be add as prefix to **all** Funcover requests.                                                                                                                                                                                                                                                                                                    |\n| `API_KEYS`               | In case you need your function to be able to run as many times as necessary, You can pass here an Appwrite API key that will be used when executing the function.\u003cbr/\u003eCheck [this](#api-key-variable-usages).                                                                                                                                                          |\n| `FLATTEN_HEADERS`        | When sets to `true` Funcover will insert all of the request headers as `headers` property inside your function payload.\u003cbr/\u003eCheck [this](#flatten-headers-key-variable-usages).                                                                                                                                                                                        |                                                                                                                                                                                                                                                \n| `PATH_AS_DATA`           | When sets to `true` Funcover will pass the following parameter as the function data.\u003cbr/\u003eCheck [this](#path-as-data-variable-usages).                                                                                                                                                                                                                                  |\n\n#### API Key variable usages\n\nThe format of this variable is like so:\n\n```\nAPI_KEYS=someProjectId:someProjectKey,anotherProjectId:anotherProjectKey\n```\n\nFirst add the project ID, then the full API key seperated with the `:` colons character.\n\nThen, if you want to another API key for another project, you can do so by separating these project keys with a `,` comma.\n\n______\n\n#### Flatten headers Key variable usages\n\nLike so:\n\n```json\n{\n  \"data\": {\n    \"data\"   : {},\n    \"headers\": {}\n  }\n}\n```\n\nNotice your `data` will be sent recursively inside `data.data` property, and you'll to extract the data like so:\n\n```javascript\n// First, Get the payload.\nconst payload = JSON.parse(req.payload);\n\n// Second, parse the data and the headers.\nconst data    = JSON.parse(payload.data);\nconst headers = JSON.parse(payload.headers);\n```\n\n______\n\n#### Path as data variable usages\n\nYou don't need to use the `data` variable like this\n\n```\nhttps://custom.domain.com/?data=data\n```\n\nYou can just send it as the following route like so:\n\n```\nhttps://custom.domain.com/data\n```\n\nThis will also work with the `ALLOW_GLOBAL` variable, so you can use it like this:\n\n```\nhttps://custom.domain.com/projectId/functionId/data\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyawitz%2Fappwrite-funcover","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbyawitz%2Fappwrite-funcover","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyawitz%2Fappwrite-funcover/lists"}