Database Engine
Secure client-to-database communication via ven.php and venjs.db. In real projects the DB logic lives in logic/*.js and the page UI lives in components/*.js.
1. Server configuration
Open ven.php and edit the $CONFIG block:
$CONFIG = [
'db_host' => '127.0.0.1',
'db_port' => 3306,
'db_name' => 'test',
'db_user' => 'root',
'db_pass' => '',
'db_charset' => 'utf8mb4',
'api_key' => 'CHANGE_THIS_TO_A_LONG_RANDOM_SECRET',
'allowed_origins' => [ 'http://localhost', 'http://127.0.0.1' ],
'allowed_tables' => [ 'users' ],
'debug' => true,
];| Key | Description |
|---|---|
db_* | PDO connection settings. |
api_key | Shared secret. Sent by the client as the X-Venjs-Key header and verified with hash_equals. Leave empty to disable (not recommended). |
allowed_origins | Origins permitted by CORS. Others get a 403. |
allowed_tables | Only these tables may be queried. |
debug | When true, exceptions return their message; set false in production. |
2. Client connection
| Config | Default | Description |
|---|---|---|
endpoint | "/ven.php" | URL of the PHP endpoint. |
apiKey | "" | Sent as the X-Venjs-Key header. |
table | null | Default table; can be overridden per call. |
headers, credentials, mode | same-origin, cors | Forwarded to fetch. |
const server = venjs.db.connect({
endpoint: "/ven.php",
apiKey: "YOUR_SECRET",
table: "users"
});3. Operations
create(data, args)
await server.create({ email: "a@b.com", name: "Ada" });register(data, args)
Like create, but hashes a password field into password_hash before insert.
await server.register({ email: "a@b.com", password: "secret" });read(args)
| Arg | Description |
|---|---|
select | Array of column names. Omit for *. |
where | Object of column: value equality conditions (AND-ed). |
orderBy | Column name; prefix with - for DESC. |
limit | 1–500. |
offset | ≥ 0. |
table | Override the default table. |
const users = await server.read({
select: ["id", "email"],
where: { email: "a@b.com" },
orderBy: "-id",
limit: 10
});update(where, data, args)
await server.update(
{ email: "a@b.com" },
{ email: "new@b.com" }
);delete(where, args) / remove(where, args)
await server.delete({ email: "new@b.com" });exists(where, args)
const { exists } = (await server.exists({ email: "a@b.com" })).data;login(credentials, args)
Verifies a plaintext password with password_verify. On success returns the selected columns (password field stripped).
const auth = await server.login(
{ email: "a@b.com", password: "secret" },
{ userField: "email", passField: "password_hash", select: ["id", "email"] }
);request(payload) & low-level
server.request(payload) sends a raw payload. The base venjs.db.request(endpoint, payload, options) performs the POST with the X-Venjs-Key header and parses the JSON response.
Response shape
{ "ok": true, "data": [ ...rows ], "count": N } // read
{ "ok": true, "data": { "id": 7 } } // create / register
{ "ok": true, "data": { "affectedRows": 1 } } // update / delete
{ "ok": true, "data": { "exists": true } } // exists
{ "ok": true, "data": { "id": 1, "email": "..." } }// loginReal-life login example
A Login page where the form UI is in components/login.js and the DB call is in logic/login.js.
logic/login.js
const loginServer = venjs.db.connect({
endpoint: "/ven.php",
apiKey: "YOUR_SECRET",
table: "users"
});
export const loginUser = async (email, password) => {
const result = await loginServer.login(
{ email, password },
{
userField: "email",
passField: "password_hash",
select: ["id", "email", "name"]
}
);
return result.data;
};components/login.js
import { loginUser } from "../logic/login.js";
const LoginPage = () => {
const email = venjs.signal("");
const password = venjs.signal("");
const status = venjs.signal("");
const loading = venjs.signal(false);
const submit = async () => {
loading.value = true;
status.value = "";
try {
const user = await loginUser(email.value, password.value);
status.value = "Welcome, " + (user.name || user.email);
password.value = "";
} catch (err) {
status.value = "Error: " + err.message;
} finally {
loading.value = false;
}
};
return venjs.div({ class: "page login-page" }, [
venjs.h1({ class: "page-title" }, "Sign in"),
venjs.input({
label: "Email",
type: "email",
value: email.value,
oninput: (e) => (email.value = e.target.value)
}),
venjs.input({
label: "Password",
type: "password",
value: password.value,
oninput: (e) => (password.value = e.target.value)
}),
venjs.button({
onclick: submit,
disabled: loading.value
}, loading.value ? "Signing in..." : "Sign in"),
status.value ? venjs.p({ class: "login-status" }, status.value) : null
]);
};
window.LoginPage = LoginPage;Real-life register example
A Register page with name, email, and password. The logic file calls register() which hashes the password automatically.
logic/register.js
const registerServer = venjs.db.connect({
endpoint: "/ven.php",
apiKey: "YOUR_SECRET",
table: "users"
});
export const registerUser = async (email, password, name) => {
const result = await registerServer.register(
{ email, password, name },
{ table: "users" }
);
return result.data;
};components/register.js
import { registerUser } from "../logic/register.js";
const RegisterPage = () => {
const name = venjs.signal("");
const email = venjs.signal("");
const password = venjs.signal("");
const status = venjs.signal("");
const loading = venjs.signal(false);
const submit = async () => {
loading.value = true;
status.value = "";
try {
const data = await registerUser(email.value, password.value, name.value);
status.value = "Account created! ID: " + data.id;
} catch (err) {
status.value = "Error: " + err.message;
} finally {
loading.value = false;
}
};
return venjs.div({ class: "page" }, [
venjs.h1({ class: "page-title" }, "Create account"),
venjs.input({
label: "Full name",
value: name.value,
oninput: (e) => (name.value = e.target.value)
}),
venjs.input({
label: "Email",
type: "email",
value: email.value,
oninput: (e) => (email.value = e.target.value)
}),
venjs.input({
label: "Password",
type: "password",
value: password.value,
oninput: (e) => (password.value = e.target.value)
}),
venjs.button({
onclick: submit,
disabled: loading.value
}, loading.value ? "Creating..." : "Sign up"),
status.value ? venjs.p({ class: "status" }, status.value) : null
]);
};
window.RegisterPage = RegisterPage;Real-life read example (user list)
A Users page that loads a list from the database. The component renders the list and shows a loading state.
logic/users.js
const usersServer = venjs.db.connect({
endpoint: "/ven.php",
apiKey: "YOUR_SECRET",
table: "users"
});
export const loadUsers = async () => {
const result = await usersServer.read({
select: ["id", "email", "name"],
orderBy: "-id",
limit: 50
});
return result.data;
};components/users.js
import { loadUsers } from "../logic/users.js";
const UsersPage = () => {
const users = venjs.signal([]);
const loading = venjs.signal(true);
const refresh = async () => {
loading.value = true;
try {
users.value = await loadUsers();
} catch (err) {
console.error(err);
} finally {
loading.value = false;
}
};
venjs.effect(() => {
refresh();
});
return venjs.div({ class: "page" }, [
venjs.h1({ class: "page-title" }, "Users"),
venjs.button({ onclick: refresh }, "Refresh"),
loading.value ? venjs.p({}, "Loading...") : null,
users.value.length ? venjs.ul({}, users.value.map(u =>
venjs.li({}, u.name + " <" + u.email + ">")
)) : venjs.p({}, "No users found.")
]);
};
window.UsersPage = UsersPage;Real-life update example
Update a user's email. The component shows a form pre-filled with the current value.
logic/users.js (add update function)
export const updateUserEmail = async (userId, newEmail) => {
const result = await usersServer.update(
{ id: userId },
{ email: newEmail }
);
return result.data;
};components/profile.js
import { updateUserEmail } from "../logic/users.js";
const ProfilePage = () => {
const email = venjs.signal("current@example.com");
const status = venjs.signal("");
const loading = venjs.signal(false);
const save = async () => {
loading.value = true;
status.value = "";
try {
const result = await updateUserEmail(1, email.value);
status.value = "Updated! Affected rows: " + result.affectedRows;
} catch (err) {
status.value = "Error: " + err.message;
} finally {
loading.value = false;
}
};
return venjs.div({ class: "page" }, [
venjs.h1({ class: "page-title" }, "Edit profile"),
venjs.input({
label: "Email",
type: "email",
value: email.value,
oninput: (e) => (email.value = e.target.value)
}),
venjs.button({
onclick: save,
disabled: loading.value
}, loading.value ? "Saving..." : "Save changes"),
status.value ? venjs.p({ class: "status" }, status.value) : null
]);
};
window.ProfilePage = ProfilePage;Real-life delete example
Delete a user by ID with a confirmation step.
logic/users.js (add delete function)
export const deleteUser = async (userId) => {
const result = await usersServer.delete({ id: userId });
return result.data;
};components/users.js (add delete button)
import { deleteUser } from "../logic/users.js";
const UsersPage = () => {
const users = venjs.signal([]);
const loading = venjs.signal(true);
const refresh = async () => {
loading.value = true;
try {
users.value = await loadUsers();
} catch (err) {
console.error(err);
} finally {
loading.value = false;
}
};
const remove = async (id) => {
if (!confirm("Delete user #" + id + "?")) return;
await deleteUser(id);
refresh();
};
venjs.effect(() => {
refresh();
});
return venjs.div({ class: "page" }, [
venjs.h1({ class: "page-title" }, "Users"),
venjs.button({ onclick: refresh }, "Refresh"),
loading.value ? venjs.p({}, "Loading...") : null,
users.value.length ? venjs.ul({}, users.value.map(u =>
venjs.li({ class: "user-item" }, [
venjs.span({}, u.name + " <" + u.email + ">"),
venjs.button({
class: "btn-danger",
onclick: () => remove(u.id)
}, "Delete")
])
)) : venjs.p({}, "No users found.")
]);
};Security reminder
- Always use
apiKeyin production and keep it secret on the client side. - Only list the tables you truly need in
allowed_tables. - Set
debug = falsein production so stack traces don't leak. - Use HTTPS in production.