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:
- 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.
- Repository URL
- The endpoint URL for your Contendo Server repository (you will get this along with your API key).
- CURL-enabled terminal
- This tool is required for making API calls.
- You can install CURL from here.
- 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
curl
commands, 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
- Add new item
- Modify item (update task status)
- Delete item
- Search for items (
Query Language
) - 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 objectcmis: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:
- Buy Some Food
- 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
andskipCount
for various API methods, including thechildren
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 toDoTask
object, 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.