Швидкий старт
Швидкий старт
Отримайте робочий API поверх вашої бази даних PostgreSQL за кілька хвилин — і викликайте його зі звичайної веб-сторінки. Ваша база даних і є вашим бекендом: без фреймворку, без шаблонного коду, без бібліотеки JavaScript. Ми крок за кроком побудуємо один «Hello World»: функцію з параметром, сповіщення в реальному часі та згенерований файл.
Увесь код цього посібника доступний як готовий приклад:
hello_world.sql (база даних) та
index.html (веб-сторінка).
Кожен endpoint докладно описано на його власній довідковій сторінці: JSON-RPC,
Сповіщення в реальному часі (SSE) та Завантаження файлів (/file).
1. Встановлення
Завантажте бінарний файл
# macOS (Homebrew)
brew install heptau/tap/pgarachne
# or download the latest release for your OS:
# https://github.com/heptau/pgarachne/releases/latest2. Налаштуйте базу даних
Створіть базу даних, службову роль, від імені якої PgArachne підключається, і завантажте схему PgArachne
(sql/schema.sql знаходиться в репозиторії):
createdb my_database
psql -d my_database -c "CREATE ROLE pgarachne LOGIN PASSWORD 'pgarachne_password'"
psql -d my_database -f sql/schema.sql3. Налаштуйте та запустіть
Створіть файл pgarachne.env (сервер знаходить його в поточному каталозі).
STATIC_FILES_PATH дозволяє PgArachne також віддавати веб-сторінку, яку ви створите на кроці 5, тож сторінка й
API мають одне джерело (origin), і налаштування CORS не потрібне:
DB_HOST=localhost
DB_PORT=5432
DB_USER=pgarachne
DB_SSLMODE=disable # local PostgreSQL without TLS only
STATIC_FILES_PATH=/path/to/web # folder with your index.html
# Optional: enables JWT sessions (POST /db//token); generate with openssl rand -hex 32
# JWT_SECRET=… Запустіть сервер (пароль службової ролі зчитується з PGPASSWORD або ~/.pgpass):
PGPASSWORD=pgarachne_password ./pgarachne4. Hello World — функція з параметром
Кожен метод API — це функція PostgreSQL з єдиним аргументом jsonb. Створіть роль для веб-сторінки та
функцію; JSON-об’єкт, який клієнт надсилає як params, надходить у функцію як payload:
CREATE ROLE demo_user WITH LOGIN PASSWORD 'user_password';
GRANT USAGE ON SCHEMA api TO demo_user;
CREATE OR REPLACE FUNCTION api.hello_world(payload jsonb DEFAULT '{}'::jsonb)
RETURNS json LANGUAGE sql AS $$
SELECT json_build_object(
'message', 'Hello, ' || COALESCE(payload->>'name', 'World') || '!');
$$;
GRANT EXECUTE ON FUNCTION api.hello_world(jsonb) TO demo_user;Доступом керує сам PostgreSQL: demo_user може викликати рівно ті функції, на які йому надано право
EXECUTE — нічого іншого недоступне.
5. Викличте її — з curl і з веб-сторінки
Ваша кінцева точка API вже працює. Ім’я користувача та пароль ролі PostgreSQL передаються за допомогою HTTP Basic-автентифікації:
curl -X POST http://localhost:8080/db/my_database/jsonrpc \
-u demo_user:user_password \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"api.hello_world","params":{"name":"Alice"},"id":1}'
# → {"jsonrpc":"2.0","result":{"message":"Hello, Alice!"},"id":1}Той самий виклик із браузера за допомогою вбудованого fetch(). Збережіть цей код як index.html у теці
з STATIC_FILES_PATH і відкрийте http://localhost:8080/:
<input id="name" value="Alice">
<button id="hello">Say hello</button>
<pre id="out"></pre>
<script>
// HTTP Basic header: "user:password" in base64 (TextEncoder handles non-ASCII)
const bytes = new TextEncoder().encode("demo_user:user_password");
const auth = "Basic " + btoa(String.fromCharCode(...bytes));
const base = location.origin + "/db/my_database"; // /{API_PREFIX}/{database}
async function rpc(method, params) {
const res = await fetch(base + "/jsonrpc", {
method: "POST",
headers: { "Authorization": auth, "Content-Type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", method, params, id: 1 }),
});
const body = await res.json();
if (!res.ok || body.error) throw new Error(body.error.message);
return body.result;
}
document.getElementById("hello").onclick = async () => {
const result = await rpc("api.hello_world", { name: document.getElementById("name").value });
document.getElementById("out").textContent = result.message; // Hello, Alice!
};
</script>6. Реальний час — нехай база даних надішле повідомлення (SSE)
PostgreSQL може публікувати повідомлення за допомогою pg_notify; PgArachne пересилає їх у браузери як Server-Sent Events.
Розширте функцію: коли виклик містить "notify": true, вона додатково публікує привітання
в канал hello:
CREATE OR REPLACE FUNCTION api.hello_world(payload jsonb DEFAULT '{}'::jsonb)
RETURNS json LANGUAGE plpgsql AS $$
DECLARE
greeting text := 'Hello, ' || COALESCE(payload->>'name', 'World') || '!';
BEGIN
IF COALESCE((payload->>'notify')::boolean, false) THEN
PERFORM pg_notify('hello', json_build_object('message', greeting)::text);
END IF;
RETURN json_build_object('message', greeting);
END; $$;На сторінці спочатку підпишіться на канал, а потім викличте функцію. Вбудований EventSource не може надіслати
заголовок Authorization, тому потік читається через fetch() (приблизно 15 рядків):
async function listen(channels, onMessage) {
const res = await fetch(base + "/sse?channels=" + encodeURIComponent(channels), {
headers: { "Authorization": auth },
});
if (!res.ok) throw new Error((await res.json()).error);
const reader = res.body.getReader(), decoder = new TextDecoder();
let buffer = "";
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let end;
while ((end = buffer.indexOf("\n\n")) >= 0) { // an event ends with a blank line
const event = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
for (const line of event.split("\n"))
if (line.startsWith("data: ")) onMessage(JSON.parse(line.slice(6)));
}
}
}
listen("hello", (m) => console.log("event:", m.data.message)); // m = {channel, data}
rpc("api.hello_world", { name: "Alice", notify: true }); // the event arrives immediatelyNOTIFY є транзакційним — PostgreSQL доставляє його лише після фіксації
транзакції функції, тобто саме тоді, коли повертається відповідь на виклик. Підписуйтеся до виклику: PostgreSQL не зберігає
сповіщення для слухачів, які ще не підключені. Канали SSE не обмежені роллю
(дивіться Сповіщення в реальному часі (SSE)).7. Завантажте файл
Функцію, що повертає рядки (path, content, mime_type, store_only), можна завантажити через кінцеву точку
/file — як сирі байти, без base64 усередині JSON. Ця повертає hello-world.md:
CREATE OR REPLACE FUNCTION api.hello_world_file(payload jsonb DEFAULT '{}'::jsonb)
RETURNS TABLE (path text, content bytea, mime_type text, store_only boolean)
LANGUAGE sql AS $$
SELECT 'hello-world.md',
convert_to('# Hello, ' || COALESCE(payload->>'name', 'World') || E'!\n\nGenerated by PostgreSQL at ' || now() || E'.\n', 'UTF8'),
'text/markdown',
false;
$$;
GRANT EXECUTE ON FUNCTION api.hello_world_file(jsonb) TO demo_user;На сторінці можна показати вміст або запропонувати його для завантаження (поверніть більше рядків — і автоматично отримаєте ZIP):
async function getFile() {
const res = await fetch(base + "/file", {
method: "POST",
headers: { "Authorization": auth, "Content-Type": "application/json" },
body: JSON.stringify({ method: "api.hello_world_file", params: { name: "Alice" } }),
});
if (!res.ok) throw new Error((await res.json()).error.message); // errors are JSON, never binary
return res.blob();
}
// show it:
console.log(await (await getFile()).text());
// or download it:
const url = URL.createObjectURL(await getFile());
const a = Object.assign(document.createElement("a"), { href: url, download: "hello-world.md" });
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url);Спробуйте з curl: curl -u demo_user:user_password -H "Content-Type: application/json" -d '{"method":"api.hello_world_file"}' http://localhost:8080/db/my_database/file -OJ.
Більше в розділі Завантаження файлів (/file).
Перш ніж іти в продакшен
- Використовуйте HTTPS. Облікові дані Basic передаються з кожним запитом; див. Розгортання та HTTPS. Змініть демонстраційні паролі.
- Зберігайте облікові дані лише в пам’яті (змінна JavaScript), ніколи в
localStorage. Щоб не надсилати пароль повторно, задайтеJWT_SECRETі отримуйте токен ізPOST /db/my_database/token(див. Безпека та автентифікація). - Веб-сторінка на іншому origin (CDN, інший порт)? Задайте
ALLOWED_ORIGINS=https://app.example.com; міждоменні запити за замовчуванням блокуються. - Найменші привілеї: одна роль PostgreSQL на кожен тип користувачів,
EXECUTEлише на потрібні їм функції, Row-Level Security для даних окремих користувачів таALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC, щоб нові функції не відкривалися випадково.
8. Підключіть ШІ-агента (MCP)
Надайте ті самі функції як інструменти для Claude Desktop або Cursor через кінцеву точку MCP:
POST http://localhost:8080/db/my_database/mcpПовні інструкції з підключення дивіться в розділі Model Context Protocol (MCP).
9. Отримайте специфікацію OpenAPI (Swagger, Postman, генерація коду)
Кожна відкрита функція також описана в документі OpenAPI 3.1, відфільтрованому до того, що може викликати автентифікований користувач:
curl http://localhost:8080/db/my_database/openapi.json \
-u demo_user:user_password
# or as YAML: .../openapi.yamlІмпортуйте URL безпосередньо в Swagger UI, Postman або генератор коду OpenAPI. Кожен метод, як і раніше, виконується через єдину кінцеву точку JSON-RPC з кроку 5 — чому специфікація перелічує окремий шлях для кожного методу без відповідного реального маршруту, пояснено в розділі Архітектурні рішення.
Це все. Прочитайте JSON-RPC, щоб ознайомитися з форматом запитів, написанням функцій, помилками та ідемпотентністю, або порівняння з PostgREST, щоб зрозуміти, яке місце посідає PgArachne.