Visit XENOBYTE.XYZ for more freeware [v1.0]


Zenode's source code is thoroughly commented, takes 10 minutes to read and includes this website as sample.
This quickstart guide is meant to complement the previous resources.

// Color nomenclature
Yellow ■: Configuration constant or function defined in app/configure.php
Green ■: Source file
Magenta ■: Code reference


By rerouting all incoming requests to app/index.php, zenode abstracts away the many necessities of backend development into a single app/core/HTTPRequest.php object that arrives fully processed at the requested controller in app/controllers/.

For example, if a client requests the base domain or homepage ( app/index.php will look for a controller file named HOME_CONTROLLER with a function declared as CONTROLLER_ENTRY_FUNCTION (both defined in app/configure.php) and pass on the processed app/core/HTTPRequest.php object for the programmer to take control of the script.

Here's a brief overview of how a client request arrives at its destination controller.

  • 1. Zenode receives a request at app/index.php
  • 2. Script is configured through app/configure.php
  • 3. Client data is processed into an app/core/HTTPRequest.php object
  • 4. app/index.php validates session related forms, IP banning and tracking, URL routing and controller validation, and passes control to the requested controller or throws an error
  • 5. The destination controller honors the request by returning the final HTML page through app/core/templateEngine.php

Most interaction between zenode and the programmer happens through app/core/HTTPRequest.php. There's also app/models/account_m.php that encapsulates account management functionality and is used by app/index.php to validate login requests. Note that app/core/HTTPRequest.php objects hold their own internal session data independently of the account they're working with, including an ID reference, email, username and account type. Should your project require more thorough user data to be stored in the SID file, the account model can also be serialized into the app/core/HTTPRequest.php object.

Directory Structure

Zenode uses an MVC formatted directory layout. Do note that there is a difference between HTML links and UNIX compliant file paths for internal use defined in app/configure.php. Actual HTML links are suffixed with "_LINK" (i.e. HOME_LINK)


index.php Script entry point

configure.php Project settings

core/ Framework internals

views/: Individual page HTML files

views/templates: Generic HTML templates (header, navbar, footer, etc.)

static/ (css, js, media): Static content (served directly as per the included NGINX config)

controllers/: URL end points by name without the .php suffix ( -> controllers/about.php)

controllers/sessions: Controllers that require sessions enabled (login & CAPTCHA controllers)

models/: Database abstractions

lib/: Libraries and miscellaneous


zenode.db Internal project database. PHP-FPM requires read and write permissions to the folder as well Database management tools (python -h)

sessions/ PHP session files

Configuring Zenode

Zenode is configured in its entirety from the monolithic configuration file app/configure.php, which is called right after app/index.php receives a request. It also contains the script's exception handler (creatively declared as handleException()) and its corresponding ERROR_CODEs.

URL Routing

All incoming requests pass through app/index.php and arrive at the requested controller file fully processed as app/core/HTTPRequest.php objects. There's no need for a routing table, controller files are filtered based on their filenames minus the .php postfix inside the CONTROLLER_DIR folder. requests app/controllers/about.php, requests app/controllers/session/logout.php and so on.

Do note that, for security reasons, the path section of the URL can only contain alphanumeric characters and separators ('/'), only the query section of the URL (after the initial argument separator '?') is decoded.

Error Handling

Errors are managed as thrown exceptions that are caught by the monolithic issue handler handleException() in app/configure.php. Both internal exceptions AND web server HTTP errors get redirected to it before ultimately returning an HTML error page with the details. Setting DEBUG to false will suppress all of PHP's internal errors, leaving only the developer defined error messages for the users to see. Exceptions are categorized and processed by their ERROR_CODE.

Database, Logging and Banning

Zenode ships with an SQLite database by default, however, the database interface app/core/database.php is generic enough to support any PDO abstraction. Make sure the CORE_SQLITE_FILE is be located in a directory that the background PHP-FPM process has read and write permissions to, the included NGINX virtual block configuration defaults to tools/zenode.db to secure it outside webserver access even though hidden files (.exmaple.txt) are already ignored by the webserver by default.

Accounts are checked for ban status after a successful login and accordingly processed. If the ban time is active the script will generate an appropriate message and throw a ERROR_CODE::ACCOUNT_BANNED exception. Ban times are set and checked by TIMEZONE in a human readable format. Individual, non-registered packet IP's can also be tagged for banning and / or tracking.

database layout (zenode/tools/zenode.db)

account All the relevant account data

ips Individual IPs to be either banned or tracked

tracked_requests Saved requests

A handy python (3+) script is included in tools/ to help manage the database (creation, backups, etc.). It can also be used to quickly ban or track IPs. Run it as python -h for the full menu.


The app/core/HTTPRequest.php object that arrives at its respective controller file already includes the user's session status and data (set to NULL if SESSIONS are disabled). A logged in user will have it's HTTPRequest->sessionStatus set to SESSION_STATUS::VALID_SESSION and the rest of the account's data valdiated by app/index.php and added to the object, while a logged out user will have its HTTPRequest->sessionStatus set to SESSION_STATUS::NO_SESSION and its account data set to null. To prevent access to privileged controllers, simply check the HTTPRequest->sessionStatus. Logging in and out is very straightforward, account hijacking can also be toggled by SESSION_ALLOW_HIJACKING.

Controllers that start sessions must be placed inside the protected folder SESSION_CONTROLLER_FOLDER to prevent unnecessary sessions and memory leaks. If the client is not logged in and calls a non-protected controller the SID file will be destroyed, if the client calls the protected controller SESSION_LOGIN_CONTROLLER and successfully logs in, the server side SID file will be saved until the user logs out, the session expires, or the account gets hijacked.

To secure an entire site from non-registered members enable LOCKDOWN_MODE.

The PHP-FPM process requires read and write permissions to the SESSION_SAVE_PATH directory, where the user's session files (SIDs) are kept.


Zenode includes its own CAPTCHA system, completely autonomous from any third-party service that will compromise security. They are dynamically generated and sent to the client alongside a form validation script (LOGIN_VALIDATION_SCRIPT_LINK in the case of logins) that helps filter wrongfully filled forms and bots. The validation script works regardless of the value of ENFORCE_LOGIN_CAPTCHA, it toggles the captcha input field status accordingly.

To add CAPTCHAS to other forms, simply add your form to the captcha delivery list in app/controllers/session/captcha.php, copy and modify the validation script in app/static/scripts/forms/login_form.js and append the resources to the form's HTML page. See app/controllers/session/login.php for an example.

Requires the gd extension installed and enabled in the php.ini.

// Zenode is free software maintained by its community
If you'd like to help the project out, consider making a donation or contributing to the development.