Building a To-Do List Application (30m)


This tutorial will walk you through the process of building a simple To-Do List Application using the Contendo Server.

What You Will Build?


By the end of this tutorial, you will have built a full-featured To-Do List Application with the ability to create, read, update, and delete To-Do List tasks. The application will interact with the Contendo Server's API, which offers powerful CRUD operations and advanced search capabilities out of the box.

You will learn to:

  • Model your To-Do List task as an object type in Contendo Server.
  • Execute API calls to perform CRUD operations.
  • Make use of Contendo Server's search functionality to retrieve To-Do List tasks.

Prerequisites


Before you begin, ensure you have the following:

  1. Contendo Server API Key
    • This key allows secure communication with the Contendo Server.
    • If you don't already have an API key, you can obtain one from here.
  2. Repository URL
    • The endpoint URL for your Contendo Server repository (you will get this along with your API key).
  3. CURL-enabled terminal
    • This tool is required for making API calls.
    • You can install CURL from here.
  4. Text editor
    • Preferably one that supports JSON syntax highlighting to make reading and editing responses easier.

How to Complete This Guide?


This tutorial is structured in a step-by-step format, allowing you to start from scratch and progressively build your To-Do List Application by executing the necessary API calls.

When copy-pasting curlcommands, do not forget to replace variables in curly brackets. Ensure that all variables are URL-encoded (e.g., replace spaces with %20) to prevent errors.

Variable Substitution

The table below provides substitute values for the variables used in curl command examples.

Variable Value
{API_KEY} Received upon signup
{REPOSITORY_URL} Received upon signup
{OBJECT_CHANGE_TOKEN} cmis:changeToken object value
{OBJECT_ID} cmis:objectId object value
{FOLDER_ID} cmis:objectId of the root folder (see section 4.1)
{OBJECT_NAME} cmis:name of the object

Step 1: To-Do List Application Design

1.1 UI mockup

We won’t actually be building the web application itself. Instead, we'll focus on defining a simple application object model. The UI mockup provided shows the structure of the app, but the core goal is to highlight how, by simply defining an object type, Contendo Server will provide the necessary API endpoints for you. In many cases, the default object types provided by Contendo Server are sufficient to create a fully functional application.


Name Details Completed
1 Buy Some Food 1kg Potato, 1 Loaf of Bread No
2 Visit Dentist Terrible toothache, Monday 1 P.M. No
Page: 1 Showing 2/2

1.2 Features

  1. Add new item
  2. Modify item (update task status)
  3. Delete item
  4. Search for items (Query Language)
  5. Pagination

Query Language is basically a SQL-92 standard subset so it should feel very familiar to any developer with some database experience.

Step 2: Create To-Do List Application Object Types


Contendo Server allows for hierarchical data structures, but in this tutorial, we'll simplify things by storing all objects directly in the root folder. The root folder is the top-level container in the content repository where all objects reside by default. It serves as the starting point for the repository's structure, and in this case, we'll be storing all objects at this level for simplicity, rather than organizing them in nested folders.

Contendo Server objects are typed, which makes them easy to manage and categorize.

2.1 Predefined Object Types:

Contendo Server offers a set of predefined object types:

  • Document: Represents standalone content with attached content streams.
  • Folder: A container for other objects, such as documents and items.
  • Relationship: A directional connection between two objects.
  • Policy: Administrative policies applied to objects.
  • Item: A generic entity, ideal for modeling To-Do List task objects.

Since our To-Do List task objects are very simple and ambiguous, we will try to use the Item object type as it seems as the best out-of-the-box candidate. Let's take a closer look at this object type in the next step.

Step 3: Inspecting the Item Object Type


To inspect the Item object type, execute the following curl command:

curl -X -k 'GET' \
  '{REPOSITORY_URL}?typeId=cmis%3Aitem&cmisselector=typeDefinition' \
  -H 'X-API-KEY: {API_KEY}'

By default, object type ID and name properties are prefixed with cmis:, such as queryName=cmis:item and baseId=cmis: item. The same applies to default type property definition names, like cmis:name=Test.

cmisselector is a request parameter used to specify the API method for HTTP GET operations

This command retrieves metadata about the Item object type. Among others, the response will contain information about the available properties of this object type, including:

  • cmis:name: We can use it as the name of the To-Do List task object
  • cmis:description: We can use it as the details of our To-Do List task object

Received response should be similar to the following JSON object with some parts omitted for brevity:

{
  "id": "cmis:item",
  "localName": "Item",
  "localNamespace": "https://codenamecode.hr/contendo",
  "displayName": "Item",
  "queryName": "cmis:item",
  "description": "Item",
  "baseId": "cmis:item",
  "creatable": true,
  "fileable": true,
  "queryable": true,
  "fulltextIndexed": true,
  "includedInSupertypeQuery": true,
  "controllablePolicy": true,
  "controllableACL": true,
  "typeMutability": {
    "create": true,
    "update": true,
    "delete": true
  },
  "propertyDefinitions": {
    "cmis:name": {
      "maxLength": 100,
      "id": "cmis:name",
      "localName": "cmis:name",
      "displayName": "Name",
      "queryName": "cmis:name",
      "description": "Name",
      "propertyType": "string",
      "cardinality": "single",
      "updatability": "readwrite",
      "inherited": false,
      "required": true,
      "queryable": true,
      "orderable": true
    },
    "cmis:description": {
      "maxLength": 100,
      "id": "cmis:description",
      "localName": "cmis:description",
      "displayName": "Description",
      "queryName": "cmis:description",
      "description": "Description",
      "propertyType": "string",
      "cardinality": "single",
      "updatability": "readwrite",
      "inherited": false,
      "required": false,
      "queryable": false,
      "orderable": false
    }
  }
}

Looking at our application's UI mockup, we can see that the default Item object type does not provide completed property so we must create our own object type.

3.1 Create toDoTask Object Type

Instead of creating a new object type from scratch, we will leverage Contendo Server's object type inheritance feature. This allows our new object to automatically inherit all properties from the Item object type outlined in the previous section.

{
  "id": "toDoTask",
  "localName": "toDoTask",
  "localNamespace": "https://codenamecode.hr/contendo",
  "displayName": "ToDo Task",
  "queryName": "toDoTask",
  "description": "ToDo Task",
  "parentId": "cmis:item",
  "baseId": "cmis:item",
  "creatable": true,
  "fileable": true,
  "queryable": true,
  "fulltextIndexed": true,
  "includedInSupertypeQuery": true,
  "controllablePolicy": true,
  "controllableACL": true,
  "typeMutability": {
    "create": true,
    "update": true,
    "delete": true
  },
  "propertyDefinitions": {
    "completed": {
      "id": "completed",
      "localName": "completed",
      "displayName": "Completed",
      "queryName": "completed",
      "description": "Task completion status",
      "propertyType": "boolean",
      "cardinality": "single",
      "updatability": "readwrite",
      "inherited": false,
      "required": true,
      "queryable": true,
      "orderable": true,
      "defaultValue": false
    }
  }
}

We can confirm that our new object type inherits from the primary Item object type by examining the values of the baseId and parentId properties. Additionally, we've introduced a new boolean property called completed.

To create toDoTask object type, execute the following curl command:

curl -X -k 'POST' \
  '{REPOSITORY_URL}' \
  -H 'Content-Type: multipart/form-data' \
  -F 'cmisaction=createType' \
  -F 'type={"id":"toDoTask","localName":"toDoTask","localNamespace":"https://codenamecode.hr/contendo","displayName":"ToDo Task","queryName":"toDoTask","description":"ToDo Task","parentId":"cmis:item","baseId":"cmis:item","creatable":true,"fileable":true,"queryable":true,"fulltextIndexed":true,"includedInSupertypeQuery":true,"controllablePolicy":true,"controllableACL":true,"typeMutability":{"create":true,"update":true,"delete":true},"propertyDefinitions":{"completed":{"id":"completed","localName":"completed","displayName":"Completed","queryName":"completed","description":"Task completion status","propertyType":"boolean","cardinality":"single","updatability":"readwrite","inherited":false,"required":true,"queryable":true,"orderable":true, "defaultValue": false}}}'

The response received should contain the newly created object type just like shown in the previous section.

cmisaction is a request parameter used to specify the API method for HTTP POST operations

Step 4: Create To-Do Task


Now that we understand the Item object type, let's create a new toDoTask object. Before doing so, we need to determine the value of the FOLDER_ID variable since it is the folder where we want to keep our tasks. In this case, it's the value of the cmis:objectId property from the root folder object. One way to find this value is by checking the repository info.

4.1 Check Repository Info for Root Folder ID

Run the following curl command to get the full repository info:

curl -X -k 'GET' \
  '{REPOSITORY_URL}?cmisselector=repositoryInfo' \
  -H 'X-API-KEY: {API_KEY}'

Received response should be similar to the following JSON object with some parts omitted for brevity:

{
  "toDoList1": {
    "repositoryId": "toDoList1",
    "repositoryName": "toDoList1",
    "repositoryDescription": "toDoList1",
    "vendorName": "codenamecode d.o.o.",
    "productName": "Contendo Server",
    "productVersion": "2024.4",
    "rootFolderId": "482b93df-6836-45fb-8963-82c20aa6e618",
    "capabilities": {},
    "aclCapabilities": {},
    "latestChangeLogToken": "a44bc091-729a-4451-abf1-3b35f83a3efd",
    "cmisVersionSupported": "1.1",
    "changesIncomplete": true,
    "changesOnType": [
      "cmis:folder",
      "cmis:document",
      "cmis:item",
      "cmis:policy",
      "cmis:relationship"
    ],
    "principalIdAnonymous": "anonymous",
    "principalIdAnyone": "anyone",
    "extendedFeatures": [],
    "repositoryUrl": "{REPOSITORY_URL}",
    "rootFolderUrl": "{REPOSITORY_URL}/root"
  }
}

We won’t dive into the details of each section of the response. Instead, we’ll focus on locating the rootFolderId property and use its value for the FOLDER_ID variable.

Run the following curl command to create our first To-Do task:

curl -X -k 'POST' \
  '{REPOSITORY_URL}/root' \
  -H 'Content-Type: multipart/form-data' \
  -F 'cmisaction=createItem' \
  -F 'succinct=true' \
  -F 'objectId={FOLDER_ID}' \
  -F 'properties={"cmis:name":"Buy Some Food","cmis:description":"1kg Potato, 1 Load of Bread","cmis:objectTypeId":"toDoTask"}'

Received response should be similar to the following JSON object confirming the creation of the new toDoTask object:

{
  "succinctProperties": {
    "cmis:baseTypeId": "cmis:item",
    "cmis:changeToken": "d22fcecd-1c1d-4628-8476-e196474a149e",
    "cmis:createdBy": "super",
    "cmis:creationDate": 1740057831703,
    "cmis:description": "1kg Potato, 1 Load of Bread",
    "cmis:lastModificationDate": 1740057831703,
    "cmis:lastModifiedBy": "super",
    "cmis:name": "Buy Some Food",
    "cmis:objectId": "3c28b871-ce03-4cd7-8f72-25abf324eb30",
    "cmis:objectTypeId": "toDoTask",
    "cmis:secondaryObjectTypeIds": null
  }
}

4.2. HTTP POST Method Content-Type

POST base HTTP methods support two types of HTTP Content-Type: multipart/form-data and application/x-www-form-urlencoded. The example above demonstrates an additional feature supported by the Contendo Server by sending all properties as a JSON in a single properties field when creating or updating objects.

There are more than 50 API methods available for repository objects manipulation.

Now you have one task in your To-Do List. Let's create the other task from our UI mockup in the next step.

Step 5: Create Another To-Do Task


To add another task, execute the following curl command:

curl -X -k 'POST' \
  '{REPOSITORY_URL}/root' \
  -H 'Content-Type: multipart/form-data' \
  -F 'cmisaction=createItem' \
  -F 'succinct=true' \
  -F 'objectId={FOLDER_ID}' \
  -F 'properties={"cmis:name":"Visit Dentist","cmis:description":"Terrible toothache, Monday 1 P.M.","cmis:objectTypeId":"toDoTask"}'

The second toDoTask object will be created, and you'll receive a similar JSON response. Now, your To-Do List contains the following tasks:

  1. Buy Some Food
  2. Visit Dentist

Step 6: Fetch All To-Do Tasks


To retrieve all toDoTask objects from your list, use the following curl command:

curl -X -k 'GET' \
  '{REPOSITORY_URL}/root?objectId={FOLDER_ID}&succinct=true&cmisselector=children' \
  -H 'X-API-KEY: {API_KEY}'

The server will return a list of all toDoTask objects in JSON format similar to this:

{
  "objects": [
    {
      "object": {
        "succinctProperties": {
          "cmis:baseTypeId": "cmis:item",
          "cmis:changeToken": "d184f782-4e42-4087-94a9-2025c15353b6",
          "cmis:createdBy": "user1",
          "cmis:creationDate": 1740405145014,
          "cmis:description": "1kg Potato, 1 Load of Bread",
          "cmis:lastModificationDate": 1740405145014,
          "cmis:lastModifiedBy": "user1",
          "cmis:name": "Buy Some Food",
          "cmis:objectId": "76710911-9201-4ef3-b27f-784bf0deedeb",
          "cmis:objectTypeId": "toDoTask",
          "cmis:secondaryObjectTypeIds": null,
          "completed": false
        }
      }
    },
    {
      "object": {
        "succinctProperties": {
          "cmis:baseTypeId": "cmis:item",
          "cmis:changeToken": "e47b4318-8705-4809-b39a-33a341a6ed68",
          "cmis:createdBy": "user1",
          "cmis:creationDate": 1740406193552,
          "cmis:description": "Terrible toothache, Monday 1 P.M.",
          "cmis:lastModificationDate": 1740406193552,
          "cmis:lastModifiedBy": "user1",
          "cmis:name": "Visit Dentist",
          "cmis:objectId": "60b29af8-003a-4fa1-8966-3be20ac46bea",
          "cmis:objectTypeId": "toDoTask",
          "cmis:secondaryObjectTypeIds": null,
          "completed": false
        }
      }
    }
  ],
  "hasMoreItems": false,
  "numItems": 2
}

6.1 Pagination

We can observe that the response includes details about our two tasks, along with additional information such as the total number of items and an indication of whether there are more items available beyond the current page. This is how we implement the pagination feature.

Contendo Server streamlines pagination implementation by enabling two additional request parameters, maxItems and skipCount for various API methods, including the children method demonstrated above. Use maxItems to define the page size and skipCount to specify the offset.


Step 7: Change To-Do Task Status


7.1 Change Token

All primary object types include an opaque string property, cmis:changeToken, which is used for optimistic locking and concurrency checking, ensuring that user updates do not conflict. The following command, which updates the status of a given task, makes use of the changeToken request parameter.

A new change token is assigned to an object upon its creation and after an update has been made.

7.2 Update Task's Status

To update the status of a toDoTaskobject, execute the following curl command:

# Request variable mapping
# ------------------------
# OBJECT_ID = object's cmis:objectId property value
# OBJECT_CHANGE_TOKEN = object's cmis:changeToken property value

curl -X -k 'POST' \
  '{REPOSITORY_URL}/root' \
  -H 'Content-Type: multipart/form-data' \
  -F 'cmisaction=update' \
  -F 'changeToken={OBJECT_CHANGE_TOKEN}' \
  -F 'objectId={OBJECT_ID}' \
  -F 'succinct=true' \
  -F 'properties={"completed":true}'

Response

The updated toDoTask object will be returned in JSON format, reflecting the new status:

{
  "succinctProperties": {
    "cmis:secondaryObjectTypeIds": null,
    "cmis:baseTypeId": "cmis:item",
    "cmis:changeToken": "202fcf25-805a-46ca-a6b4-f50d0581f5bc",
    "cmis:createdBy": "user1",
    "cmis:creationDate": 1740405145014,
    "cmis:description": "1kg Potato, 1 Load of Bread",
    "cmis:lastModificationDate": 1740406408917,
    "cmis:lastModifiedBy": "user1",
    "cmis:name": "Buy Some Food",
    "cmis:objectId": "76710911-9201-4ef3-b27f-784bf0deedeb",
    "cmis:objectTypeId": "toDoTask",
    "completed": true
  }
}

Step 8: Fetch Single To-Do Task


You can fetch a single toDoTask object using various methods. Let's explore a few:

8.1 Fetch by Object ID

curl -X -k 'GET' \
  '{REPOSITORY_URL}/root?objectId={OBJECT_ID}&succinct=true&cmisselector=object' \
  -H 'X-API-KEY: {API_KEY}'

8.2 Fetch by Object Path

curl -X -k 'GET' \
  '{REPOSITORY_URL}/root/{OBJECT_NAME}?succinct=true&cmisselector=object' \
  -H 'X-API-KEY: {API_KEY}'

Several Contendo Server API methods also support using the object path in place of the objectId request parameter.

The path to an object can be specified as follows: /{domain_name}/{repository_id}/root/{folder_1_name}/.../{folder_n_name}/{object_name}.

This hierarchical structure defines the object's location within the domain repository, starting from the root folder and traversing through nested folders to the specific object.

Path to the root folder is always given as /{domain_name}/{repository_id}/root.

8.3 Fetch Using Query Language (Search)

curl -X -k 'POST' \
  '{REPOSITORY_URL}' \
  -H 'Content-Type: multipart/form-data' \
  -F 'cmisaction=query' \
  -F 'succinct=true' \
  -F 'statement=SELECT cmis:name, cmis:description FROM toDoTask where cmis:objectId='\''{OBJECT_ID}'\'''

At the start of this tutorial, we introduced an input field for searching tasks. The statement field value from the curl request above represents what you would enter to search for tasks. This is how we implement the search functionality.

The query will always return a list of results with only the properties you request. Use an asterisk (*) in the SELECT clause to retrieve all properties.


Step 9: Delete To-Do Task


To delete a To-Do item, execute the following curl command:

curl -X -k 'POST' \
  '{REPOSITORY_URL}/root' \
  -H 'accept: */*' \
  -H 'X-API-KEY: {API_KEY}' \
  -H 'Content-Type: multipart/form-data' \
  -F 'cmisaction=delete' \
  -F 'objectId={OBJECT_ID}' \
  -F 'allVersions=true'

Upon success, you'll receive an HTTP status 200 code indicating the item was successfully deleted.


Conclusion


In this tutorial, you built a To-Do List Application using the Contendo Server. You learned how to create, retrieve, update, and delete To-Do List items, while exploring advanced querying techniques. You've also gained the foundational knowledge required to integrate Contendo Server into more complex applications.