Docs / Backend / Database Engine
ven.php

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,
];
KeyDescription
db_*PDO connection settings.
api_keyShared secret. Sent by the client as the X-Venjs-Key header and verified with hash_equals. Leave empty to disable (not recommended).
allowed_originsOrigins permitted by CORS. Others get a 403.
allowed_tablesOnly these tables may be queried.
debugWhen true, exceptions return their message; set false in production.

2. Client connection

const server = venjs.db.connect(config);
ConfigDefaultDescription
endpoint"/ven.php"URL of the PHP endpoint.
apiKey""Sent as the X-Venjs-Key header.
tablenullDefault table; can be overridden per call.
headers, credentials, modesame-origin, corsForwarded 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)

ArgDescription
selectArray of column names. Omit for *.
whereObject of column: value equality conditions (AND-ed).
orderByColumn name; prefix with - for DESC.
limit1–500.
offset≥ 0.
tableOverride 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": "..." } }// login

Real-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 apiKey in production and keep it secret on the client side.
  • Only list the tables you truly need in allowed_tables.
  • Set debug = false in production so stack traces don't leak.
  • Use HTTPS in production.