Bearweb Database

Bearweb CMS is a database-driven framework. This article demonstrates the structure of the database, including how to create, edit and maintain the database.

--by @ Jan 24, 2026

Index

One of the reasons I created my own CMS is because of the complex database of existing CMSs. Therefore, when I designed this CMS, I aimed to make the underlying database structure simple, portable and manageable.

Bearweb CMS is database-driven, each module has its own private database. Bearweb CMS is designed with SQLite in mind, but should be easily adapt to other relational DBMS. For SQLite, each database is a file; therefore, using dedicated database (file) for each module should minimize congestion.

Bearweb CMS has a very simple database structure. It has 3 modules (sitemap, session and transaction, user), it relies on only 4 databases in 3 SQLite database files:

In Bearweb CMS, modules are database-backed classes instances. An instance is a row in the table. Each variable in the instance can be mapped to a field in the module's database table, with the same field name for variable name. int will be saved as integer in the database, human-readable string will be saved as text, array and object will be saved as JSON text, binary string will be saved as blob.

Bearweb CMS is portable. It is designed to use a portable DBMS, the SQLite. Transferring the website from one machine to another requires copying and pasting the PHP files (the CMS) and the database files (the data).

Make sure that the database files and the directory containing the database files are readable and writable.

Although it is recommended to modify the website through APIs, we all like the quick-and-dirty way. The database is very managementable, by design, there is no foreign key or trigger. You can directly make modifications to the database to make changes to the website, as long as you:

Don't worry. If Brarweb CMS senses a data in bad format, it will use the default value.

Bearweb Sitemap

Requesting a webpage, a file (for example, an image), or any other resource is in fact fetching data from the database, with access control and some data manipulation. This database is called Sitemap because the table is a map of the website.


CREATE TABLE "Sitemap" (
	"url"		TEXT NOT NULL UNIQUE,
	"category"	TEXT NOT NULL DEFAULT '',
	"template"	TEXT NOT NULL DEFAULT '["object", "blob"]',
	"owner"		TEXT NOT NULL DEFAULT '',
	"create"	INTEGER NOT NULL DEFAULT 0,
	"modify"	INTEGER NOT NULL DEFAULT 0,
	"meta"		TEXT NOT NULL DEFAULT '{}',
	"content"	BLOB DEFAULT '',
	"aux"		TEXT NOT NULL DEFAULT '{}',
	PRIMARY KEY("url")
) WITHOUT ROWID
	

Bearweb_Site Database Structure

Column Type (SQLite) Default Description Example
url text, PK, Unique, Not null No default value Resource URL. All resources are indexed by its URL.
  • 'hello/world.html' - Use this URL to access this resource.
category text, Not null '' Resource category, for management purpose, ignored by Bearweb CMS framework.
  • 'Article' - This resource is an "Article".
template JSON array in text, Not null '["object", "blob"]' - Object (file) template, direct blob output. Template used to process this resource. Bearweb CMS will invoke template[0] (template), then template[0] will invoke template[1] (sub-template).
  • '['page-en', 'direct']' - Bearweb CMS will invoke page-en.php in the Bearweb_Site::Dir_Template directory, then, page-en.php will invoke page-en_direct.php in the Bearweb_Site::Dir_Template directory.
owner text, Not null '' (empty string) - System-owned resource. Owner's user ID of this resource. Only the owner and admin (group 0) can modify this resource.
  • '' - The system owns this resource. Such as generated resource. (Example: system generated RSS sitemap)
  • 'John' - User with ID "John" owns this resource. Such as articles by the user.
create int, Not null 0 (Bearweb_Site::TIME_NULL) - No actual time. Create timestamp.
  • 0 - No actual time, such as generated resource. (Example: system generated RSS sitemap)
  • 60 - Resource created at 1970-01-01 00:01:00.
modify int, Not null 0 (Bearweb_Site::TIME_NULL) - No actual time. Modify timestamp.
  • 0 - No actual time, such as API.
  • 60 - Resource modified at 1970-01-01 00:01:00.
meta JSON object in text, Not null '{}' - Empty metadate. Resource metadata. Optional data used by Bearweb CMS framework and the template.
  • '{"title": "Example Title", "description": "This is an example resource."}' - See below.
content text, or null '' - Empty content. Resource content, the content should be directly output to reduce server process load (for a webpage, that means use HTML code instead of markdown). If null, Bearweb will try read a file with the same pathname as the url under the resource directory. This is used to offload large files from the database. See my blog for details.
  • '<div><!-- My content in HTML --></div>' - An article.
  • 'ABCDEFG' - A plain text file.
  • 48613FA87E... - A binary file.
  • null - File-backed resource. The Bearweb CMS will try read a file with the same pathname as the url under the resource directory.
aux JSON object in text, Not null '{}' - Empty aux data. Resource auxiliary data. Optional data used by the template.
  • '{"lang-en": "url/en", "lang-zh": "url/zh"}' - See below.

meta and aux store optional data. It doesn't make sense to create seperate database field for each of those optional data (which will make the table extremely wide and unmanageable, and create a lot of unused field); therefore, they are aggregated into JSON objects.

Bearweb_Site meta options

Key Type Description Used by template with example
title string Title All
  • '{"title": "Example Title"}' - Page title is "Example Title".
keywords string, comma-seperated words Keywords All
  • '{"keywords": "keyword1, keyword2, keyword3"}' - Page with 3 keywords: "keyword1", "keyword2" and "keyword3".
description string Description All
  • '{"description": "This is an example resource."}' - Page description is "This is an example resource.".
r301 string Permanently redirect to target URL. Core
  • '{"r301": "new/page.html"}' - Cause Bearweb to send an HTTP 301 Moved Permanently header to "/new/page.html". Note this value is an absolute URL without leading "/".
r302 string Temporarily redirect to target URL. Core
  • '{"r301": "new/page.html"}' - Cause Bearweb to send an HTTP 302 Found header to "/new/page.html". Note this value is an absolute URL without leading "/".
robots string Tells crawlers to bot not index and/or follow. CoreAll
  • '{"r301": "noindex"}' - Do not index this resource.
  • '{"r301": "nofollow"}' - Do not links on this resource.
  • '{"r301": "noindex, nofollow"}' - Both above.

This is a request, but some inresponsible crawlers won't respect it.

Do not use anything other than noindex, nofollow or noindex, nofollow. Some crawlers cannot understant other attributes.

access array of number and string If specified, Bearweb will enable access control on this resource. Only user id or user group in the array can read this resource; others will received a HTTP 403 error. Note, Bearweb CMS uses string for user ID and int for user group. Core
  • '{"access": ["Alice", 23, "Bob"]}' - Allows user ID "Alice" and "Bob" and all users in group 23 to read this resource.
  • '{"access": []}' - Allows nobody to read this resource.
  • The owner and admin (group 0) always have access.
img string URL to a poster image. All
  • '{"bgimg": "sun.jpg"}' - Use "/sun.jpg" as poster for this page. Note this value is an absolute URL without leading "/".
Image
  • '{"hd": "sun.thumb.jpg"}' - If in image sub-template, use this image as thumbnail. Note this value is an absolute URL without leading "/".
hd string URL to a HD image. Image
  • '{"hd": "sun.jpg"}' - Use "/sun.jpg" as the high definition image for this page. Note this value is an absolute URL without leading "/".
ratio number Provide the aspect ratio (width / height) of this image. Image
  • 1.33333 - It helps the browser to provide the correct space when layout the webpage before the image is downloaded. This is used when generate bulletin page.
task API task Specify task to execute. All
  • '{"task": "login"}' - Execute the "login" task.

All those data are optional. Bearweb will use default value or skip it if a specific data is not given.

Bearweb_Site aux options

Key Type Description Used by template with example
lang-* string Alternative language for multilingual resources. All
  • '{"lang-en": "url/en", "lang-zh": "url/zh"}' - Specify the target URLs for the languages (including the current language and URL). Note these values are absolute URLs without leading "/".
mime string Object MIME type All
  • '{"mime": "image/png"}' - Cause the object template to send a Content-Type: image/png HTTP header (PNG image).
  • If not specified, object template will send a Content-Type: text/plain HTTP header (plain text).

All those data are optional. Bearweb will use default value or skip it if a specific data is not given.

Bearweb Session

A transaction represents a HTTP request from the client-side to the server-side and the corresponding response from the server-side to the client-side.

For Bearweb CMS, a request can be divided into 3 parts:

For Bearweb CMS, a session is a series of transactions from the same client.

Bearweb CMS uses two databases to record transactions and sessions.


CREATE TABLE "Transaction" (
	"id"		TEXT NOT NULL UNIQUE,
	"create"	INTEGER NOT NULL DEFAULT -1,
	"ip"		TEXT NOT NULL DEFAULT '',
	"url"		TEXT NOT NULL DEFAULT '',
	"status"	INTEGER NOT NULL DEFAULT 0,
	"time"		INTEGER NOT NULL DEFAULT -1,
	"memory"	INTEGER NOT NULL DEFAULT -1,
	"session"	TEXT NOT NULL DEFAULT '',
	"log"		TEXT NOT NULL DEFAULT '',
	PRIMARY KEY("id")
) WITHOUT ROWID,STRICT
	

CREATE TABLE "Session" (
	"id"		TEXT NOT NULL UNIQUE,
	"create"	INTEGER NOT NULL DEFAULT -1,
	"lastuse"	INTEGER NOT NULL DEFAULT -1,
	"user"		TEXT NOT NULL DEFAULT '',
	"key"		TEXT NOT NULL DEFAULT '',
	PRIMARY KEY("id")
) WITHOUT ROWID,STRICT
	

Bearweb_Session.Transaction Database Structure

Column Type (SQLite) Default Description Example
id text, PK, Unique, Not null No default value Transaction ID.
  • 'AZaz09+=' - Use this ID to identify a transaction.
create int, Not null -1 - Invalid value, this shouldn't happen. Create timestamp.
  • 60 - Transaction created at 1970-01-01 00:01:00. This is the exact time when the server received the request, not when the client send the request.
ip text, Not null '' Client-side IP address and port, it can be a IPv4 address or a IPv6 address.
  • '1.2.3.4:10086' - Client IP address and port.
  • '1234:5678:90ab:cdef:ff00:5599:abcd:1234:12345' - Client IP address and port.
url text, Not null '' Request resource or API URL.
  • 'hello/world.html' - The URL of the accessed resource or API.
status int, Not null 0 - Invalid value, unhandled exception occured. Response HTTP code.
  • 200 - Server responsed a HTTP 200 OK to the client.
  • 404 - Server responsed a HTTP 404 Not Found to the client.
  • 500 - Server responsed a HTTP 500 Internal Server Error to the client.
  • 0 - An unhandled exception occurred. The PHP script exited before it writes the HTTP response code to the database.
time int, Not null -1 - Invalid value, unhandled exception occured. Time in microseconds used to process this trqansaction. This includes the time server waiting for data transfer (this can be a very large number, like tens of seconds, if sends a file). This value comes from (microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]) * 1e6.
  • 25000 - Server uses 25ms to process this request.
  • -1 - An unhandled exception occurred. The PHP script exited before it writes the time to the database.
memory int, Not null -1 - Invalid value, unhandled exception occured. Memory usage in kiB used to process this trqansaction. This value comes from memory_get_peak_usage(false) / 1024.
  • 1024 - Server uses 1 MiB of memory to process this request.
  • -1 - An unhandled exception occurred. The PHP script exited before it writes the memory usage to the database.
session text, Not null '' The ID of the corresponding session.
  • 'zaZA90=+' - Use this ID to identify the corresponding session.
log text, or null '' - Empty log. Debug use. Use $BW->log('Hello world'); to append a new line of log.
  • 'Hello\nStart\nDone!' - Some log.
  • '' - No log. Or,
  • '' - An unhandled exception occurred. The PHP script exited before it writes the log to the database.

Bearweb_Session.Session Database Structure

Column Type (SQLite) Default Description Example
id text, PK, Unique, Not null No default value Session ID.
  • 'zaZA90=+' - Use this ID to identify a session.
create int, Not null -1 - Invalid value, this shouldn't happen. Create timestamp.
  • 60 - Session created at 1970-01-01 00:01:00. This is the exact time when the server received the request, not when the client send the request.
lastuse int, Not null -1 - Invalid value, this shouldn't happen. Last use timestamp.
  • 120 - Session last used at 1970-01-01 00:02:00. This is the exact time when the server received the request, not when the client send the request.
user text, Not null '' - No user binded (guest). Session binded user ID.
  • '' - Guest session.
  • 'John' - Session binded to user with ID 'John'.
key text, Not null '' - Invalid. A key used to verify the session.
  • '90=+zaZA' - Session key.

Session and Transaction Database Maintenance

To view the transaction and its corresponding session, use a view:


CREATE VIEW `Transaction_View` AS
	select all
		t.`id`,
		datetime(t.`create`, 'unixepoch') as `create`,
		t.`ip`,
		t.`url`,
		t.`status`,
		t.`time` as `time (us)`,
		t.`memory` as `memory (kB)`,
		t.`log`,
		t.`session`,
		datetime(s.`create`, 'unixepoch') as `firstuse`,
		datetime(s.`lastuse`, 'unixepoch') as `lastuse`,
		s.`user`,
		s.`key`
	from `Transaction` t join `Session` s where t.`session` = s.`id`
order by `create` desc
	

The transaction and session database grows over time. You may want to occasionally clean expired sessions and old transactions:


delete from `Session` where strftime('%s', 'now') - `lastuse` > 30 * 24 * 3600;
delete from `Transaction` where strftime('%s', 'now') - `create` > 30 * 24 * 3600;
vacuum;
	

Bearweb User

Bearweb saves user in a database.


CREATE TABLE "User" (
	"id"		TEXT NOT NULL UNIQUE,
	"name"		TEXT NOT NULL DEFAULT '',
	"salt"		TEXT NOT NULL DEFAULT ':',
	"password"	TEXT NOT NULL DEFAULT ':',
	"registertime"	INTEGER NOT NULL DEFAULT 0,
	"lastactive"	INTEGER NOT NULL DEFAULT 0,
	"group"		TEXT NOT NULL DEFAULT '{}',
	"data"		TEXT NOT NULL DEFAULT '{}',
	PRIMARY KEY("id")
) WITHOUT ROWID,STRICT
	

Bearweb_User Database Structure

Column Type (SQLite) Default Description Example
id text, PK, Unique, Not null No default value User ID.
  • 'John' - Use this ID to identify a user.
name text, Not null '' User's nickname. This name can be duplicated.
  • 'John Doe' - User's nickname.
salt text, Not null ':' - Invalid value. Salt for password.
  • 'AZaz09+=' - The salt used to hash the password.
password text, Not null ':' - Invalid value. Password after salt 32-byte (256-bit) cipher.
  • 'zaZA90=+' - The password after hash using the salt.
registertime int, Not null 0 Create timestamp.
  • 60 - Account created at 1970-01-01 00:01:00. This is the exact time when the server received the request, not when the client send the request.
lastactive int, Not null 0 Last active timestamp.
  • 120 - Account last active at 1970-01-01 00:02:00. This is the exact time when the server received the request, not when the client send the request.
group JSON array in text, Not null '[]' - No group. User's group. Use int for group ID. Group 0 is for "admin".
  • '[0, 1]' - User is in admin group and "Group 1".
  • '[114]' - User is in "Group 114".
data JSON object in text, Not null '{}' - Empty data. Other data of this user.
  • '{"key1": "value 1", "key2": 114514}' - User data.