Content Manipulation
The restapi does not only expose content objects via a RESTful API. The API consumer can create, read, update, and delete a content object. Those operations can be mapped to the HTTP verbs POST (Create), GET (Read), PUT (Update) and DELETE (Delete).
Manipulating resources across the network by using HTTP as an application protocol is one of core principles of the REST architectural pattern. This allows us to interact with a specific resource in a standardized way:
| Verb | URL | Action |
|---|---|---|
| POST | /folder | Creates a new document within the folder |
| GET | /folder/{document-id} | Request the current state of the document |
| PATCH | /folder/{document-id} | Update the document details |
| DELETE | /folder/{document-id} | Remove the document |
Creating a Resource with POST
To create a new resource, we send a POST request to the resource container. If we want to create a new document within an existing folder, we send a POST request to that folder:
POST /news HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Content-Type: application/json
{
"@type": "Page",
"title": "My News Item",
"description": "News Description"
}
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.createContent({
token: login.data.token,
path: '/news',
data: {
'@type': 'Page',
title: 'My News Item',
description: 'News Description',
},
});
By setting the ‘Accept’ header, we tell the server that we would like to receive the response in the ‘application/json’ representation format.
The ‘Content-Type’ header indicates that the body uses the ‘application/json’ format.
The request body contains the minimal necessary information needed to create a document (the type and the title). You could set other properties, like “description” here as well.
Successful Response (201 Created)
If a resource has been created, the server responds with the 201 Created status code. The ‘Location’ header contains the URL of the newly created resource and the resource representation in the payload:
HTTP/1.1 201 Created
Content-Type: application/json
{
"title": "My News Item",
"description": "News Description",
"@components": {
"actions": {
"@id": "http://localhost:8080/news/@actions"
},
"breadcrumbs": {
"@id": "http://localhost:8080/news/@breadcrumbs"
},
"catalog": {
"@id": "http://localhost:8080/news/@catalog"
},
"inherit": {
"@id": "http://localhost:8080/news/@inherit"
},
"navigation": {
"@id": "http://localhost:8080/news/@navigation"
},
"navroot": {
"@id": "http://localhost:8080/news/@navroot"
},
"related": {
"@id": "http://localhost:8080/news/@related"
},
"translations": {
"@id": "http://localhost:8080/news/@translations"
},
"types": {
"@id": "http://localhost:8080/news/@types"
},
"workflow": {
"@id": "http://localhost:8080/news/@workflow"
}
},
"@id": "http://localhost:8080/news/my-news-item",
"@type": "Page",
"id": "my-news-item",
"created": "2022-04-08T16:00:00.000Z",
"modified": "2022-04-08T16:00:00.000Z",
"UID": "a95388f2-e4b3-4292-98aa-62656cbd5b9c",
"is_folderish": true,
"layout": "view",
"owner": "admin",
"review_state": "private",
"lock": {
"locked": false,
"stealable": true
}
}
Unsuccessful Response (400 Bad Request)
If the resource could not be created, for instance because the title was missing in the request, the server responds with 400 Bad Request:
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"message": "Required field(s) missing."
}
The response body can contain information about why the request failed.
Reading a Resource with GET
After a successful POST, we can access the resource by sending a GET request to the resource URL:
GET /news HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.getContent({
token: login.data.token,
path: '/news',
});
Successful Response (200 OK)
If a resource has been retrieved successfully, the server responds with 200 OK:
HTTP/1.1 200 OK
Content-Type: application/json
{
"title": "News",
"blocks": {
"79ba8858-1dd3-4719-b731-5951e32fbf79": {
"@type": "title"
}
},
"description": "News Items",
"blocks_layout": {
"items": [
"79ba8858-1dd3-4719-b731-5951e32fbf79"
]
},
"items": [],
"@components": {
"actions": {
"@id": "http://localhost:8080/news/@actions"
},
"breadcrumbs": {
"@id": "http://localhost:8080/news/@breadcrumbs"
},
"catalog": {
"@id": "http://localhost:8080/news/@catalog"
},
"inherit": {
"@id": "http://localhost:8080/news/@inherit"
},
"navigation": {
"@id": "http://localhost:8080/news/@navigation"
},
"navroot": {
"@id": "http://localhost:8080/news/@navroot"
},
"related": {
"@id": "http://localhost:8080/news/@related"
},
"translations": {
"@id": "http://localhost:8080/news/@translations"
},
"types": {
"@id": "http://localhost:8080/news/@types"
},
"workflow": {
"@id": "http://localhost:8080/news/@workflow"
}
},
"@id": "http://localhost:8080/news",
"@type": "Folder",
"id": "news",
"language": {
"title": "English",
"token": "en"
},
"created": "2022-04-02T20:22:00.000Z",
"modified": "2022-04-02T20:22:00.000Z",
"effective": "2022-04-02T20:22:00.000Z",
"UID": "32215c67-86de-462a-8cc0-eabcd2b39c26",
"owner": "admin",
"is_folderish": true,
"layout": "view",
"review_state": "published",
"lock": {
"locked": false,
"stealable": true
}
}
For folderish types, their childrens are automatically included in the response as items.
Unsuccessful response (404 Not Found)
If a resource could not be found, the server will respond with 404 Not Found:
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": "Not found: /random"
}
GET Responses
Possible server reponses for a GET request are:
- 200 OK
- 404 Not Found
- 500 Internal Server Error
Updating a Resource with PATCH
To update an existing resource we send a PATCH request to the server. PATCH allows to provide just a subset of the resource (the values you actually want to change).
If you send the value null for a field, the field’s content will be deleted. Note that this is not possible if the field is required.
PATCH /news/my-news-item HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Content-Type: application/json
{
"title": "My New News Item"
}
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.updateContent({
token: login.data.token,
path: '/news/my-news-item',
data: {
title: 'My New News Item',
},
});
Successful Response (204 No Content)
A successful response to a PATCH request will be indicated by a 204 No Content response by default:
HTTP/1.1 204 No Content
Removing a Resource with DELETE
We can delete an existing resource by sending a DELETE request:
DELETE /news/my-news-item HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.deleteContent({
token: login.data.token,
path: '/news/my-news-item',
});
A successful response will be indicated by a 204 No Content response:
HTTP/1.1 204 No Content
DELETE Repsonses
- 204 No Content
- 404 Not Found (if the resource does not exist)
- 405 Method Not Allowed (if deleting the resource is not allowed)
- 500 Internal Server Error
Reordering sub resources
The resources contained within a resource can be reordered using the ordering key using a PATCH request on the container.
Use the obj_id subkey to specify which resource to reorder. The subkey delta can be ‘top’, ‘bottom’, or a negative or positive integer for moving up or down.
PATCH /news HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Content-Type: application/json
{
"ordering": {"obj_id": "my-news-item", "delta": "top"}
}
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = cli.orderContent({
token: login.data.token,
path: '/news',
data: {
obj_id: 'my-news-item',
delta: 'top',
},
});
Exporting a Resource with GET
We can use the @export endpoint to export the content to a json-file to be used for migrations or external services. We can export the resource by sending a GET request to the resource URL:
GET /news/@export HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.exportContent({
token: login.data.token,
path: '/news',
});
Successful Response (200 OK)
If a resource has been retrieved successfully, the server responds with 200 OK:
HTTP/1.1 200 OK
Content-Type: application/json
{
"title": "News",
"blocks": {
"79ba8858-1dd3-4719-b731-5951e32fbf79": {
"@type": "title"
}
},
"description": "News Items",
"blocks_layout": {
"items": [
"79ba8858-1dd3-4719-b731-5951e32fbf79"
]
},
"language": {
"title": "English",
"token": "en"
},
"created": "2022-04-02T20:22:00.000Z",
"modified": "2022-04-02T20:22:00.000Z",
"effective": "2022-04-02T20:22:00.000Z",
"owner": "admin",
"layout": "view",
"type": "Folder",
"uuid": "32215c67-86de-462a-8cc0-eabcd2b39c26",
"workflow_state": "published"
}
Exporting ICS format
We can use the ics_view endpoint to export the event content to an iCalendar file to be used for external services. We can export the data by sending a GET request to the resource URL:
GET /events/event-1/ics_view HTTP/1.1
Accept: text/calendar
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.getICS({
token: login.data.token,
path: '/events/event-1',
});
Successful Response (200 OK)
If a resource has been retrieved successfully, the server responds with 200 OK:
HTTP/1.1 200 OK
Content-Type: text/calendar
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nick//NONSGML Nick//EN
X-WR-TIMEZONE:UTC
BEGIN:VEVENT
SUMMARY:Event 1
DTSTART:20260401T124909Z
DTSTAMP:20220101T000000Z
UID:405ca717-0c68-43a0-88ac-629a82658675@http://localhost:3000
CREATED:20220402T201000Z
LAST-MODIFIED:20220402T201000Z
URL:http://localhost:3000/events/event-1
DTEND:20260408T124911Z
RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20260408T124900Z
END:VEVENT
END:VCALENDAR
Exporting ICS format on a path
We can also use the ics_view endpoint to export multiple events. When we call the ics_view on a path it will fetch all events from this path down in an iCalendar file. We can export the data by sending a GET request to the resource URL:
GET /events/ics_view HTTP/1.1
Accept: text/calendar
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.getICS({
token: login.data.token,
path: '/events',
});
Successful Response (200 OK)
If a resource has been retrieved successfully, the server responds with 200 OK:
HTTP/1.1 200 OK
Content-Type: text/calendar
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nick//NONSGML Nick//EN
X-WR-TIMEZONE:UTC
BEGIN:VEVENT
SUMMARY:Event 1
DTSTART:20260401T124909Z
DTSTAMP:20220101T000000Z
UID:405ca717-0c68-43a0-88ac-629a82658675@http://localhost:3000
CREATED:20220402T201000Z
LAST-MODIFIED:20220402T201000Z
URL:http://localhost:3000/events/event-1
DTEND:20260408T124911Z
RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20260408T124900Z
END:VEVENT
BEGIN:VEVENT
SUMMARY:Event 2
DTSTART:20250401T124909Z
DTSTAMP:20220101T000000Z
UID:455ca717-0c68-43a0-88ac-629a82658675@http://localhost:3000
CREATED:20230402T201000Z
LAST-MODIFIED:20230402T201000Z
URL:http://localhost:3000/events/event-2
DTEND:20250408T124911Z
RRULE:FREQ=DAILY;INTERVAL=1;UNTIL=20250408T124900Z
END:VEVENT
END:VCALENDAR
Exporting RSS format on a path
We can call the rss_view endpoint to export the content in RSS format. When we call the rss_view on a path it will fetch all content from this path down in a RSS file. We can export the data by sending a GET request to the resource URL:
GET /events/rss_view HTTP/1.1
Accept: application/rss+xml
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImZ1bGxuYW1lIjoiQWRtaW4iLCJpYXQiOjE2NDkzMTI0NDl9.RS1Ny_r0v7vIylFfK6q0JVJrkiDuTOh9iG9IL8xbzAk
Or use the client directly:
import { Client } from '@robgietema/nick';
const cli = Client.initialize({ apiPath: 'http://localhost:8080' });
const login = await cli.login({
data: { login: 'admin', password: 'admin' },
});
const { data } = await cli.getRSS({
token: login.data.token,
path: '/events',
});
Successful Response (200 OK)
If a resource has been retrieved successfully, the server responds with 200 OK:
HTTP/1.1 200 OK
Content-Type: text/calendar
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Events</title>
<description></description>
<language>en</language>
<link>http://localhost:8080/events</link>
<lastBuildDate>Sat, 01 Jan 2022 00:00:00 GMT</lastBuildDate>
<managingEditor>admin@example.com (Admin)</managingEditor>
<item>
<title>Event 1</title>
<link>http://localhost:8080/events/event-1</link>
<guid>405ca717-0c68-43a0-88ac-629a82658675</guid>
<pubDate>Sat, 02 Apr 2022 20:10:00 GMT</pubDate>
<description></description>
</item>
<item>
<title>Event 2</title>
<link>http://localhost:8080/events/event-2</link>
<guid>455ca717-0c68-43a0-88ac-629a82658675</guid>
<pubDate>Sun, 02 Apr 2023 20:10:00 GMT</pubDate>
<description></description>
</item>
</channel>
</rss>