Nick: When Backend meets Frontend, Plone Conference 2025
robgietema
0 views
112 slides
Oct 16, 2025
Slide 1 of 112
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
About This Presentation
This talk will show you how you can create a site using Nick, show the differences compared with Plone as a backend and a new of integrating frontend and backend systems.
Size: 1.87 MB
Language: en
Added: Oct 16, 2025
Slides: 112 pages
Slide Content
NICK
A NEARLY HEADLESS CMS
Plone Conference 2025, Jyväskylä
-
WHEN BACKEND MEETS FRONTEND
THE STATE OF NICK
Rob Gietema@robgietema
ABOUT ME
WHAT IS NICK?
(Nearly) Headless CMS
Build with Node.js
RESTfull API compatible with plone.restapi (Volto)
WHY "NICK"?
Nearly Headless CMS
WHY?
Fun to build!
Plone has a great architecture, great way to learn
the internals
Plone has a great Rest API
Started as a proof of concept on Ploneconf 2018 in
Tokyo
Frontend and backend using the same language
WEBSITE
https://nickcms.org
ONLINE DEMO
https://demo.nickcms.org
DOCUMENTATION
https://docs.nickcms.org
CONTRIBUTE
https://github.com/robgietema/nick
WHAT WILL WE COVER?
What does the architecture look like?
How does Nick perform?
How to build a site using Nick
How to integrate with a backend
WHAT DOES THE ARCHITECTURE
LOOK LIKE?
ISSUES WITH PLONE
Disclaimer: my opinion
Lots of legacy code
Lot of code to maintain ourself
Deployment
Application Plone Nick
PLONE VS NICK
Language PythonNode
Storage ZODB Postgres
Lines of code~1.250.000~8.800
HOW DOES NICK PERFORM?
TEST SETUP
MacBook M1 Max
Plone 6.0.13
Nick 2.9.0
Postman
API test only
* Disclaimer
READS
Fetch the siteroot, 100 concurrent users
WRITES: PLONE
Create a Page, 20 concurrent users
WRITES: NICK
Create a Page, 20 concurrent users
DELETE
Delete a Page with 1000 children
Nick: 136 msPlone: 2187 ms
DELETE
Delete a Page with 10000 children
Nick: 400 msPlone: 29691 ms
RENAME
Rename a Page with 10000 children
Nick: 4429 msPlone: 83941 ms
HOW TO BUILD A SITE USING NICK
Bootstrap a project
Configuration file
Profiles
Contenttypes
Behaviors
Initial Content
Permissions, Users,
Groups & Workflows
Vocabularies
Catalog & Search
Events
Controlpanels
i18n
Logging
Tests
Docs
WHAT WILL WE COVER?
CREATE THE DATABASE
CREATE DATABASE "myproject";
CREATE USER 'myproject' WITH ENCRYPTED PASSWORD 'myproject';
GRANT ALL PRIVILEGES ON DATABASE "myproject" TO "myproject";
ALTER DATABASE "myproject" OWNER TO "myproject";
YEOMAN GENERATOR
$ npm install -g yo
$ npm install -g @robgietema/generator-nick
$ yo @robgietema/nick myproject
DOCUMENT MODEL
/**
* Document Model.
* @module models/document/document
*/
/**
* A model for Document.
* @class Document
* @extends Model
*/
export class Document extends Model {
...
/**
ID_TITLE_FROM_YEAR.JS
/**
* Id and title from year behavior.
* @module behaviors/id_title_from_year/id_title_from_year
*/
import { uniqueId } from '@robgietema/nick/src/helpers/utils/u
/**
* Id and title from year behavior.
* @constant id_title_from_year
*/
export const id_title_from_year = {
/**
* Set id
*@methodsetId
INITIAL CONTENT
profiles/default/documents/schedule-2025.nick.json
{
"uuid": "605ca717-0c68-43a0-88ac-629a82658675",
"type": "Talk",
"title": "Nick: When Backend meets Frontend",
"description": "Nick is a nearly headless CMS written in Nod
"firstname": "Rob",
"lastname": "Gietema",
"bio": "Rob is a frontend webdeveloper for over 25 years. He
"picture": "/images/rob.png",
"length": "Long",
"level": "Beginner",
"owner": "robgietema",
"workflow_state": "approved"
}
VERSIONS
profiles/default/documents/schedule-2025.nick.json
{
"uuid": "605ca717-0c68-43a0-88ac-629a82658675",
"type": "Talk",
"title": "Nick: When Backend meets Frontend",
"description": "Nick is a nearly headless CMS written in Nod
"firstname": "Rob",
"lastname": "Gietema",
"bio": "Rob is a frontend webdeveloper for over 25 years. He
"picture": "/images/rob.png",
"length": "Long",
"level": "Beginner",
"owner": "robgietema",
"workflow_state": "approved"
"workflow_history": [
{
CONVERSION FROM PLONE
Korqtv"wukpi"rnqpg0ezrqtvkorqtv
pnpm convert <inputfolder> <outputfolder>
PERMISSION SYSTEM
Permissions
Roles (have permissions)
Groups (have roles)
Users (have roles, groups)
Local roles (user/group has a role on an object)
Local role permissions are inherited from the parent
Local role inheritence can be disabled per object
Workflows (have states and transitions)
States (have permissions per role)
Transitions (have permissions)
AUTHOR INDEX
/**
* Author index behavior.
* @module behaviors/author_index/author_index
*/
/**
* Author index behavior.
* @constant author_index
*/
export const author_index = {
/**
* Get author
* @method author
* @param {Object} trx Transaction object.
*@returns{String}author
TOTALTIME INDEX
/**
* Total time index behavior.
* @module behaviors/total_time_index/total_time_index
*/
import { map } from 'lodash';
/**
* Total time index behavior.
* @constant total_time_index
*/
export const total_time_index = {
/**
* Get total time
*@methodtotalTime
TYPES.TEST.JS
import app from '@robgietema/nick/src/app';
import { testRequest } from '@robgietema/nick/src/helpers';
describe('Types', () => {
it('should return the schedule type', () =>
testRequest(app, 'types/schedule'));
});
DOCS
DOCS
myproject
└─ docs
└─ index.md
└─ types.md
INDEX.MD
---
layout: default
nav_exclude: true
---
# My Project
## Introduction
My awesome project!
TYPES.MD
---
nav_order: 1
permalink: /types
---
# Types
## Get the schema with GET
To get the schema of a content type, access the `/@types` endp
```
{% include_relative examples/types/schedule.req %}
```
DOCS
HOW TO INTEGRATE WITH A
BACKEND
BACKEND FOR FRONTEND (BFF)
CREATE CONTENT USING THE API
POST /news HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"@type": "Page",
"title": "My News Item",
"description": "News Description"
}
CHAT ENDPOINT
POST /@chat HTTP/1.1
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ
Content-Type: application/json
{
"prompt": "What is the largest land animal?",
"context": [ ... ],
"messages": [ ... ],
}
RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json
{
"model": "qwen3",
"created_at": "2025-01-01T00:00:00.00000Z",
"response": "The largest land animal is the African bush ele
"done": true,
"done_reason": "stop",
"context": [
...
]
"total_duration": 356186167,
"load_duration": 18592125,
"promptevalcount":139,
QUESTIONS?
Want to implement a site using Nick? Talk to me!
But what about AI? See my talk later today!
slideshare.net/robgietema/nick-when-backend-meets-frontend
github.com/robgietema/nick-example