DEV Community

Cover image for Bidding farewell to Appwrite's Tasks Service 👋
Bradley Schofield for Appwrite

Posted on

Bidding farewell to Appwrite's Tasks Service 👋

Appwrite's Tasks Service was a great way to send HTTP requests to remote URLs at specific intervals based on a CRON schedule. This allowed external services to react to events that take place within Appwrite and perform actions based on the request.

We deprecated Tasks in Appwrite 0.12 because Appwrite's Cloud Functions can now serve the same purposes as Tasks and can give you even more control than Tasks ever could! With Functions, you will be able to respond to events from Appwrite directly and easily recreate the Tasks functionality!!

🤔 What is Appwrite?
Appwrite is an open source backend-as-a-service that abstracts all the complexity involved in building a modern application by providing you with a set of REST APIs for your core backend needs. Appwrite handles user authentication and authorization, databases, file storage, cloud functions, webhooks, and much more! If anything is missing, you can extend Appwrite using your favorite backend language.

The removal of the Tasks Service is a breaking change, and you may need to account for this before you migrate from 0.11 to prevent the functionality of your application from being broken. In this article, we'll see how to migrate your existing Tasks to Functions.

👷‍♂️ Recreating the Tasks functionality using Functions

We will be using NodeJS for this example because it's quick and easy to write. I'll be making sure to explain what the code is doing so you can follow along and use this base even if you don't know JavaScript!

First, you want to create a new Function within Appwrite. Navigate to the Functions Service in the Appwrite dashboard, and then click New Function. Within the following popup, make sure to set the runtime to "Node 16.0" and give it a name of your choosing.

Next, create a folder somewhere on your computer to start writing code. Don't worry, you won't be writing too much code. By creating a folder to store everything, you make it easier to package it later. Now, create a new file called main.js. This file is where we will be holding all of our code, so let's paste the following code into the file (Don't worry, I'll break it down and explain it in a moment 🙂)

// Configure this section
const reqURL =  new URL(process.env["REQUEST_URL"]);
const method = 'GET';
const AuthCredentials = {
    username: process.env["USERNAME"],
    password: process.env["PASSWORD"]
}
const headers = { 
// You can also add additional custom headers here.
    'User-Agent': 'Appwrite-Server',
    'X-Appwrite-Task-UID': process.env['APPWRITE_FUNCTION_ID'],
    'X-Appwrite-Task-Name': process.env['APPWRITE_FUNCTION_NAME']
};
// End of configuration

const client = reqURL.protocol == 'https' ? require('https') : require('http');

if (AuthCredentials.username && AuthCredentials.password) {
    headers['Authorization'] = 'Basic ' + Buffer.from(`${AuthCredentials.username}:${AuthCredentials.password}`).toString('base64');
}

const req = client.request({
    hostname: reqURL.hostname,
    port: reqURL.port,
    path: reqURL.pathname,
    method: method,
    headers: headers
}, (res) => {
    if (!(res.statusCode >= 200 && res.statusCode < 300)) {
        throw new Error('Request failed with status code: ' + res.statusCode);
    }
})

req.end();
Enter fullscreen mode Exit fullscreen mode

At its core, it's a function that makes a simple HTTP request. It may look a little complicated because I have opted to use no dependencies, so it is effortless to package. Let me break it all down:

const reqURL =  new URL(process.env["REQUEST_URL"]);
const method = 'GET';
const AuthCredentials = {
    username: process.env["USERNAME"],
    password: process.env["PASSWORD"]
}
const headers = { // You can add custom headers here.
    'User-Agent': 'Appwrite-Server',
    'X-Appwrite-Task-UID': process.env['APPWRITE_FUNCTION_ID'],
    'X-Appwrite-Task-Name': process.env['APPWRITE_FUNCTION_NAME']
};
Enter fullscreen mode Exit fullscreen mode

The section above is the one you will be interested in the most. It holds all the configuration for this function and is the equivalent to the (now deprecated)Task dialogue from the Tasks Service. The only difference is that it's in code instead of the UI.

reqURL: This is the URL that we'll be making the request to. Make sure to specify the https:// or http:// protocol at the beginning. You'll see why later!

method: The HTTP method to use when making the request.

authCredentials: These will be credentials that will be used when making the request. They are encoded using the HTTP Basic Authentication method. You can leave username and password empty, if your endpoint does not require authentication.

headers: These are the headers that will be used when making the request. A few things of note here:

  • User-Agent will always be 'Appwrite-Server', just like in the old Tasks service.
  • X-Appwrite-Task-UID is now the Function's ID
  • X-Appwrite-Task-Name is the Function's Name Feel free to add additional headers here if you like.

Now the rest is all the inner workings of this function. If you don't care about this, you can skip this section.

👨‍💻 Breakdown of the Code

const client = reqURL.protocol == 'https' ? require('https') : require('http');
Enter fullscreen mode Exit fullscreen mode

I was referencing this when I stated that you should remember to set protocol when defining your URL. This is because NodeJS uses different modules to make an HTTP or HTTPS request. This section of code simply automates the selection of the correct module.

if (AuthCredentials.username && AuthCredentials.password) {
    headers['Authorization'] = 'Basic ' + Buffer.from(`${AuthCredentials.username}:${AuthCredentials.password}`).toString('base64');
}
Enter fullscreen mode Exit fullscreen mode

This section computes the Basic Authentication header if it's required and sets it.

const req = client.request({
    hostname: reqURL.hostname,
    port: reqURL.port,
    path: reqURL.pathname,
    method: method,
    headers: headers
}, (res) => {
    if (!(res.statusCode >= 200 && res.statusCode < 300)) {
        throw new Error('Request failed with status code: ' + res.statusCode);
    }
})
Enter fullscreen mode Exit fullscreen mode

This final section makes the request, using all the previously declared variables with an arrow function to check if the status code was successful and throw an error if it isn't.

📦 Packaging the function

Packaging and using the function is simple! There are multiple ways to package an Appwrite function, and I'll go through the two most common ones.

🚀 Appwrite CLI

The Appwrite CLI is a powerful tool for interacting and developing with Appwrite. We highly recommend it as it makes many tasks to do with Appwrite a breeze, including packaging an Appwrite function! You can read more about the Appwrite CLI and how to install it in our docs.

Open your terminal and run the following command on Linux or macOS:

appwrite functions createTag \
    --functionId=YourFunctionID \
    --command='node main.js' \
    --code='/path/to/folder/with/function'
Enter fullscreen mode Exit fullscreen mode

or on Powershell:

appwrite functions createTag `
    --functionId=YourFunctionID `
    --command='node main.js' `
    --code='/path/to/folder/with/function'
Enter fullscreen mode Exit fullscreen mode

Make sure to replace YourFunctionID with the ID of the function you created earlier and replace /path/to/folder/with/function with the path to the folder you created to contain the main.js file.

Then go back to Appwrite and activate the tag you created using the command above.

Finally, you can navigate to the Settings tab of your function and set the CRON Schedule back to the one it was. It follows the same rules as the Tasks Service's CRON Schedule. You can also configure all the environment variables like the username, password and the endpoint in here.

Once you're happy with the configuration don't forget to click the Update button at the bottom.

💻 Manual Packaging

You can also manually package the code into a tarball. Open up a terminal and navigate to the folder where you created your main.js file earlier. Then run the following command:

tar -cf code.tar.gz main.js
Enter fullscreen mode Exit fullscreen mode

This command should create a code.tar.gz file within the directory where your code is.

Next, you want to navigate to your Appwrite console, return to the Functions service, and click on the function you created earlier. Within the overview tag of the function, click the Deploy Tag button, then click on Manual inside this dialogue, and set the command to:

node main.js
Enter fullscreen mode Exit fullscreen mode

Finally, set the Gzipped Code (tar.gz file) field to the code.tar.gz file you created earlier, click on the Create button, and make sure you click the Activate button next to the tag you just created.

Finally, you can navigate to the Settings tab of your function and set the CRON Schedule back to the one it was. It follows the same rules as the Tasks Service's CRON Schedule. You can also configure all the environment variables like the username, password and the endpoint in here.

Once you're happy with the configuration don't forget to click the Update button at the bottom.

🎉 Congratulations!

You just successfully migrated your Task over to Functions! Wasn't that easy? Not only that, but you've levelled up the possibilities of your task 💪 Now you can call the URL when an Appwrite event happens, giving you even greater control of what your task can do!

You can use the following resources to learn more and get help

Discussion (0)