DEV Community

Cover image for Building a free TickTick API with IFTTT and AWS
Andrew Schmelyun
Andrew Schmelyun

Posted on

Building a free TickTick API with IFTTT and AWS

This article doesn't expect you to have any in-depth familiarity with AWS or IFTTT. However, it is required that you have accounts for both, and recommended that you've at least played around with both of them a little bit. The free tiers of both of these services is all that's required to get your personal API running, so this doesn't cost a single cent.

Disclaimer: This is not intended to replace a full-featured API and only works for your own TickTick account. It was built for a very simple purpose of mine, but depending on your use case, it could be perfect for you as well.

Anyway, let's get started! 🎉

Backstory

Over the past couple of months I've been experimenting with a little hardware project, the goal being to display a current to-do item I'm focusing on through an e-ink screen using a Raspberry Pi. On the program's startup, I was calling an API to grab my to-do list and store the items in an array, then cycling through each to display on my screen.

A problem came up when after the New Year I switched services to TickTick. I had been thinking about moving away from the last app that I used, and the fact that they have a built-in Pomodoro timer solidified the switch for me and I haven't looked back.

But, they don't have an open API.

It seems like it's an item that has been requested a few times, and currently seems to be an item that they're working on. But as of the writing of this article, they do not have one available to the public. I get it though, pushing new features to production takes time, but that was time that I didn't want to wait. So, I started digging around trying to find my own way.

I thought I'd just go the brute force method and use something like Puppeteer to sign in to the app and scrape out any data I could. But then I remembered IFTTT exists, and decided to see if they happened to have a trigger for tasks from TickTick. Lo and behold, they did! So my plan started coming together.

Here's how I did it, and how you can too.

Getting Started

First thing's first, if you haven't already, sign up for an Amazon AWS account. This is what will end up powering our actual API, using the following services:

  • Lambda for processing incoming data from our API endpoints and sending it to/getting it from our database
  • API Gateway for creating our endpoints and connecting them to our Lambda instances
  • DynamoDB for being the NoSQL database storing our todo items

All of these services have generous free usage tiers that don't expire. Unless you're going to be using this API every minute, you shouldn't incur any direct costs.

Everything we need to do in order to set up these three services is going to be handled through the command line using the serverless app. We'll be loosely following this tutorial to create our serverless AWS API.

Go ahead and open up a terminal or command prompt window, and run the following command to get the serverless app installed:

npm install serverless -g
Enter fullscreen mode Exit fullscreen mode

Side Note: Don't have Node or npm installed? Check out this link and finish that before continuing with the above command.

Once that's completed, run this command to get the serverless todo demo API installed locally. This is going to be the basis for our API:

serverless install -u https://github.com/serverless/examples/tree/master/aws-node-rest-api-with-dynamodb -n ticktick-api
Enter fullscreen mode Exit fullscreen mode

Navigate to the new folder created, ticktick-api, and open up the code with your favorite editor.

Now it's time to start working on our API.

Creating our Serverless API

After opening up your new project, you should see a file structure that looks like this:

ticktick-api/
├── todos/
│   ├── create.js
│   ├── delete.js
│   ├── get.js
│   ├── list.js
│   └── update.js
├── package.json
├── README.md
└── serverless.yml
Enter fullscreen mode Exit fullscreen mode

It's a pretty simple layout, each one of the files under the todos directory is a specific endpoint for our API. They're going to be powered by a Node Lambda instance, interact with our database, and return something back in the request. The serverless.yml file ties everything together, and generates the AWS services that we need in this API.

Before we actually deploy this though, we'll need to make some slight adjustments based on the data that we're pulling in and storing.

Opening up create.js, you'll see the following lines of code:

if (typeof data.text !== 'string') {
  console.error('Validation Failed');
  callback(null, {
    statusCode: 400,
    headers: { 'Content-Type': 'text/plain' },
    body: 'Couldn\'t create the todo item.',
  });
  return;
}

const params = {
  TableName: process.env.DYNAMODB_TABLE,
  Item: {
    id: uuid.v1(),
    text: data.text,
    checked: false,
    createdAt: timestamp,
    updatedAt: timestamp,
  },
};
Enter fullscreen mode Exit fullscreen mode

This isn't bad, but I'd like to add the ability to store the list from TickTick as well as the todo item's text. So, let's make some adjustments to add that datapoint in there as well:

if (typeof data.text !== 'string' || typeof data.list !== 'string') {
  console.error('Validation Failed');
  callback(null, {
    statusCode: 400,
    headers: { 'Content-Type': 'text/plain' },
    body: 'Couldn\'t create the todo item.',
  });
  return;
}

const params = {
  TableName: process.env.DYNAMODB_TABLE,
  Item: {
    id: uuid.v1(),
    text: data.text,
    list: data.list,
    checked: false,
    createdAt: timestamp,
    updatedAt: timestamp,
  },
};
Enter fullscreen mode Exit fullscreen mode

Okay, that's good. Let's open up our update.js file as well and add that list attribute there too. I won't show the before here, but this is what lines 12-37 should look like with both the text and list attributes:

if (typeof data.text !== 'string' || typeof data.list !== 'string' || typeof data.checked !== 'boolean') {
  console.error('Validation Failed');
  callback(null, {
    statusCode: 400,
    headers: { 'Content-Type': 'text/plain' },
    body: 'Couldn\'t update the todo item.',
  });
  return;
}

const params = {
  TableName: process.env.DYNAMODB_TABLE,
  Key: {
    id: event.pathParameters.id,
  },
  ExpressionAttributeNames: {
    '#todo_text': 'text',
    '#todo_list': 'list',
  },
  ExpressionAttributeValues: {
    ':text': data.text,
    ':list': data.list,
    ':checked': data.checked,
    ':updatedAt': timestamp,
  },
  UpdateExpression: 'SET #todo_text = :text, #todo_list = :list, checked = :checked, updatedAt = :updatedAt',
  ReturnValues: 'ALL_NEW',
};
Enter fullscreen mode Exit fullscreen mode

Lastly, open up serverless.yml and remove the frameworkVersion: ">=1.1.0 <2.0.0" line entirely. Save that file, and we're ready to deploy.

Back in the command line, run the following commands to install any dependencies, bundle everything together, and deploy your API:

npm install

serverless deploy
Enter fullscreen mode Exit fullscreen mode

You should get back a set of endpoints like this:

Service Information
service: ticktick-api
stage: dev
region: us-east-1
api keys:
  None
endpoints:
  POST - https://xxxxx.execute-api.us-east-1.amazonaws.com/dev/todos
  GET - https://xxxxx.execute-api.us-east-1.amazonaws.com/dev/todos
  GET - https://xxxxx.execute-api.us-east-1.amazonaws.com/dev/todos/{id}
  PUT - https://xxxxx.execute-api.us-east-1.amazonaws.com/dev/todos/{id}
  DELETE - https://xxxxx.execute-api.us-east-1.amazonaws.com/dev/todos/{id}
functions:
  ticktick-api-dev-update: arn:aws:lambda:us-east-1:12345:function:ticktick-api-dev-update
  ticktick-api-dev-get: arn:aws:lambda:us-east-1:12345:function:ticktick-api-dev-get
  ticktick-api-dev-list: arn:aws:lambda:us-east-1:12345:function:ticktick-api-dev-list
  ticktick-api-dev-create: arn:aws:lambda:us-east-1:12345:function:ticktick-api-dev-create
  ticktick-api-dev-delete: arn:aws:lambda:us-east-1:12345:function:ticktick-api-dev-delete
Enter fullscreen mode Exit fullscreen mode

Note: If you get an error back about AWS provider credentials not being found, follow this guide to set them up in order to work with your serverless CLI app.

Alright, so now you should be able to visit your API endpoint at https://xxxxx.execute-api.us-east-1.amazonaws.com/dev/todos, and as of right now it should be returning back an empty array.

If so, we're on the right track, and we can move on to getting data from TickTick into it.

Setting up IFTTT

Okay, let's head over to IFTTT and either create or sign in to your account. This is going to function as the tie between TickTick and our newly-created API.

Create a new Applet, and for the service select TickTick. You're presented with two possible triggers, of which we're going to use both. But for this first one, select New task created.

Screenshot of IFTTT triggers for the TickTick service. Options are New task created, or New completed task

On the next screen, you'll determine if you want to limit this by only firing on select task lists, tags, or priorities. Me personally, I'm using the Inbox list, with all tags and any priority, so it looks like this:

Screenshot of the options for a TickTick trigger in IFTTT. Options shown are List, with Inbox selected, Tag, with no selection, and Priority, with no selection.

Hit create, and we're brought back to the Applet creation screen for the TT in IFTTT.

Select the Webhooks service, and on the following page choose the only action available, Make a web request.

On this page, we're going to add in all of the details for the web request. The URL is going to be one of the ones returned to us by serverless earlier, and should end in just /dev/todos.

Set the method to POST, and the Content Type to application/json.

Finally, for the body, we're going to create a JSON string that will also contain data from our TickTick to-do item, pulled in automatically with the help of IFTTT.

All of these form fields should look like the screenshot below:

Screenshot of a Web request action in IFTTT. The options are URL which says https://xxxxx.execute-api.us-east-1.amazonaws.com, Method which has POST selected, Content Type which has application/json selected, and Body which has a JSON string containing text TaskName and list List.

Once that's saved, we're ready to test it out!

Head over to your TickTick app, and create a new to-do item. When making the trigger in IFTTT, if you set any of the optional requirements like a specific list or tag, be sure to set those to make sure everything is working correctly.

Within a minute or so, we should be able to load up the main /dev/todos API endpoint in a browser and get back an array containing any of the items that we added to TickTick in our test. They each should contain the task name, list name, timestamps, and a unique ID. That ID can then be used to pull in a single item, update it, or delete it from the database altogether.

Marking an item complete

You may have noticed that the above only pulls in items to our API when they're created. But, what if we want to update an item in our API when it's checked off in TickTick?

We can do that! First head back to the IFTTT dashboard and create a new Applet, with TickTick as the service. This time, choose the other trigger, New completed task. Again, choose what filters you would like (lists, tags, etc), and moving onto the action step, select Webhooks.

We're going to make a request to an endpoint on our API called /dev/todos/complete. Don't worry, we'll create this shortly. Use the PUT Method, application/json Content Type, the same Body from the create Applet, and save it.

Now, let's open our ticktick-api code back up, and take a look at the update.js file. This normally would expect an ID in the URL along with data attributes posted to it, in order to update a todo item. However, we're going to do some slight modifications in order to determine what item from what list needs to be updated, and mark it of as checked in our DynamoDB instance.

This is what that updated file should look like:

'use strict';

const AWS = require('aws-sdk');

const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports.update = (event, context, callback) => {
  const timestamp = new Date().getTime();
  const data = JSON.parse(event.body);

  const params = {
    TableName: process.env.DYNAMODB_TABLE,
    ExpressionAttributeValues: {
      ':text': 'text',
      ':list': 'list',
    },
    FilterExpression: 'list = :list and text = :text'
  };

  dynamoDb.scan(params, function (error, result) {

    result.Items.forEach(function (element, index, array) {
      const updateParams = {
        TableName: process.env.DYNAMODB_TABLE,
        Key: {
          id: event.pathParameters.id,
        },
        ExpressionAttributeNames: {
          '#todo_text': 'text',
          '#todo_list': 'list',
        },
        ExpressionAttributeValues: {
          ':text': data.text,
          ':list': data.list,
          ':checked': true,
          ':updatedAt': timestamp,
        },
        UpdateExpression: 'SET #todo_text = :text, #todo_list = :list, checked = :checked, updatedAt = :updatedAt',
        ReturnValues: 'ALL_NEW',
      }

      dynamoDb.update(updateParams, (error, result) => {
        const response = {
          statusCode: 200,
          body: JSON.stringify(result.Attributes),
        };
        callback(null, response);
      });
    });
  });
};
Enter fullscreen mode Exit fullscreen mode

Now all we have to do is run serverless deploy from our terminal again to update our Lambda instances and we're good to go! If we check off an item in TickTick, that same item in our API will have a checked: true attribute.

That's it!

You now have your very own personal TickTick REST API that you can use to store items you've added in your lists and reference them programmatically in other applications. If you have any questions about this, please feel free to let me know in the comments.

Follow me here or on Twitter for more updates on what I'm using this API for, and other web development projects I'm working on.

Top comments (2)

Collapse
 
thatguyfrodo profile image
Ben

Is it possible to update the data in Tick Tick with this api? Or does it just update the local copy? I was able to get a list of todo's using the ics file and display them on a web page. That works great. Except that my kids have to tell me when they complete something, so that's annoying. Ultimately I'm hoping to be able to check an item as completed from their dashboard web page.

Collapse
 
aschmelyun profile image
Andrew Schmelyun

Unfortunately no, it only works one-way with this setup. However, if you have the know-how there's a few third party libraries that provide a more accessible API that you can use. I've seen two talked about most often, one in Python and one in Node.