Backend (middleware) development
[]
Warning Backend development and frontend development are mutually exclusive and should not be done at the same time.
Local development setup
Requirements
HCMS CSK local installation
As a first step before the actual development, the application needs to be run locally (usually on a developer's machine) in a docker-compose setup.
Developer machine
- Node 16
- yarn
- install by
npm install -g yarn
- install by
- A native compilation toolchain, required by some
npm
libraries. On OS X, this is a default part of the Xcode development environment and/or standard OS.- C and C++ compiler
- GNU Make
- python (version 3)
GNU Make
GNU cp
on Mac OS X- Homebrew package
coreutils
orgcp
- optional;
- Homebrew package
A testing environment for deployments
Also, prior to any customization, we strongly recommend to prepare an environment that fully imitates the future productive environment with the correct future domain and always to install a fully working application on it for testing purposes. This ensures that all customizations will work as expected and nothing is broken due to an incorrect server setup.
Steps
Deploy frontend files
Warning This setup is not meant for the frontend development. Please do not try to do both frontend and backend development at once!
Running middleware without frontend files (mainly .js
but also others) is possible, but will lead to limited functionality since one of the main purposes of the middleware is to server them.
Run
make -f deployment/docker/Makefile dist
Alternatively, you can run the following step in the Dockerfile. It takes the
hcms-client
image and extracts the frontend files:BASHrm -rf dist docker build -t hcms-client:local -f deployment/docker/Dockerfile . mkdir dist docker run --rm hcms-client:local tar zcf - . | (cd dist && tar zxvf - )
Check that the following sources exist and note the full path to them:
dist/schemas
| the directory with all schemas from all modulesdist/web
| the directory with all static files to be served (all the JavaScript files, webpacked and minified)dist/module-infos.json
| the file with all modules and correct paths to JavaScript files- This file is very important, because filenames contain hash code!
Deploy backend
The following commands deploy the middleware and the HTTP server locally.
cd backend
yarn install
yarn workspaces run build
- Note that before compiling TypeScript, some files are generated. For this reason, this step must be run at least once.
- make sure that
local-portal-data
existsmkdir local-portal-data
- note that it is in
.gitignore
, so it does not pollute git workspace - the
local-portal-data/portal.json
file will contain portal configuration; it can be safely deleted and everything will return to "factory defaults"
- create
config.json
- use <code>config.local.json</code> as a template, filling (or removing) all the
TODO:
values- The only exception is the mail-server configuration (
mailConfig
). The application will run without it, with some limitations.
- The only exception is the mail-server configuration (
- set
webDir
andmoduleInfosFile
to full paths to frontend build results- this is actually optional; remove both these properties to run without any frontend
- set
availableModules
to list of available modules, or make sure it's not set at all (note that this behavior can change in future) - everything inside
portal
property is optional if you already have full portal definition (local-portal-data/portal.json
)- usually, though, the
portal.json
contains placeholder of crucial information (domain, smtp server, hcms url) - removing these fromconfig.json
means that the result will be invalid (unresolved placeholders)
- usually, though, the
- note that the
config.json
is in.gitignore
, so creating it does not modify the git repository
- use <code>config.local.json</code> as a template, filling (or removing) all the
- When using the local docker-compose setup: set the environment variable
GLOBAL_AGENT_HTTP_PROXY
(to use the http proxy) andNODE_TLS_REJECT_UNAUTHORIZED
(to accept self-signed certificate)export GLOBAL_AGENT_HTTP_PROXY=http://localhost:3128
export NODE_TLS_REJECT_UNAUTHORIZED=0
- run the backend
yarn run start
- or directly
node core/build/Server.js
- or just directly run
Server.js
from IDE (or evenServer.ts
, if the IDE is able to handle typescript)
Thelocal-portal-data/portal.json
file is created on the first run as this is actually a standard behaviour of the standalone variant.
Hooks module
After you deployed the backend, the hooks will be still missing. Even if added (as explained below), they won't be invoked as they would use the localhost
URLs, but it is not possible to use localhost
inside the Docker container in the current setup.
For this reason, a complete local testing of hooks is very tricky, if at all possible. It requires HCMS to be run locally too, and without docker
, but then that eliminates all advantages of the Docker Compose setup.
It is, however, possible to develop changes, cover them by unit tests and even fully test them by crafting HTTP requests manually.
cd backend/hooks
yarn install
yarn run build
andyarn run test
- add
../hooks/build/MediaPortalHooks.js
into thescripts
array inbackend/core/hooks/config.json
JSON"scripts": ["../build/ExpressAdapter.js", "../hooks/build/MediaPortalHooks.js"]
- run the backend using
yarn run start
- all the
/hook/
* endpoints will be now available
- all the
What you can customize and where you should do it
Below you will find an information about the functonalities that are customizable and where you can add your custom code in the respective source code.
Schema development
The most important (and usually the first) part of development is creating new schemas and changing existing ones. This is covered by a separate document.
Portal Configuration, multi-domain application
This document describes the concepts of portal configuration and domain lookup, and the reason why the code is quite complicated.
In most cases, there is only one single DNS domain and none of this is actually necessary in production. Even in that case, however, the local development needs to support alternative domains (localhost:3000
, localhost:3390
).
Main startup script and configuration
What it does
Main entrypoint of the whole application is the backend/core/src/Server.ts, which:
- loads the configuration file
config.json
and the environment variableCONFIG
and merges them together- the
CONFIG
variable is optional, but theconfig.json
is not
- the
- initializes the HTTP server (<code>express</code>), in particular:
- serving of static files (mostly compiled javascript), with special support of compressed javascript
- cookie support
- trust of reverse proxy headers like
X-Forwarded-For
: set to always enabled- except
X-Forwarded-Host
, which is disabled by default (it is not set by many reverse proxies, leaving a huge security hole when enabled)
- except
- automated addition of such security headers as
Strict-Transport-Security
,X-Content-Type-Options
,X-Frame-Options
,Referrer-Policy
,Permissions-Policy
, etc. - support for request bodies: either JSON or raw bytes
- limited to 10 MB (note: real data upload never goes through nodejs middleware!)
- CORS headers handling, if enabled in configuration
/health.txt
endpoint used as a basic health-check
- runs all the scripts specified by the configuration property
scripts
in theconfig.json
- normally, this is the core <code>ExpressAdapter.ts</code> and the webhook handler <code>Hook.ts</code>
The configuration file is parsed as a generic any
type, but it should conform to the MainConfiguration
interface in <code>Types.ts</code>. Standard configuration properties are also described in detail here.
What can be customized
- Headers: you can add more security headers. Check the official browser documentation for available headers, e.g., https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#security.
- Request bodies: you can add support for other types (multipart to handle direct form submit, XML, or YAML) or change a limit for the request body size.
REST API
The script <code>ExpressAdapter.ts</code> serves as the main entrypoint to the portal core itself. The factory
function creates HTTP request handles. factory
is based upon express.Router()
method of Express.js. Please consult the official Express.js documentation for more information. This is one of the places to add a completely new API. The default four ones, however, should be sufficient in most cases. See RestApi.ts. Most of the ExpressAdapter.ts
's code is actually handling a very special edge case: an application running on several domains. This special case, however, is not officially supported because it is quite hard to configure it correctly and safely.
The best place is marked by special comment in the ExpressAdapter.ts
file, search for CUSTOM API HERE
.
Class MediaApi
in the <code>MiddlewareApi.ts</code> script can server as a template for any new API implementations. It can be also directly enhanced if a new image- or video- related endpoint is needed.
The function makeJwtValidator
in the <code>JwtValidation.ts</code> script is a convenient way to ensure that the API segment is accessible only for the logged-in users. The function can be simply added before any real request handler (the usual express
pattern). As a side effect, it also adds the decoded token (ie all its claims) to the request object, so the handler can use it for some fine grained access control.
Development cycle and link to frontend
It is also strongly recommended setting up an automatic deployment pipeline (CI/CD) for deploying custom code, in accordance with the established best practices. Although the backend development is usually a smaller part of the whole project effort.
- Change the code (TypeScript) and/or schema (the HCMS schema file).
- Compile and run the
node
application - Check that the schemas are all applied without errors.
- In case of error, fix the schema and restart the application.
- Check that the application works
- Invoke
http://localhost:3000/data/config.json
- Invoke
- Test the new/modified REST endpoint (if any).
- Build the new docker image of the whole application and deploy it to the dev environment, if any.
- Ideally, this is done by a CI/CD server.
- Check that changes are also available on the dev environment.
- When ready, deploy the new image to the testing/production environment according to any agreed process.
Warning: All frontend developers will need the new image too. They can either build it locally (inefficient) or just docker pull
it from a shared repository (much more efficient). Please communicate your changes accordingly!