Szybki start
Szybki start
Uzyskaj działające API na bazie swojej bazy danych PostgreSQL w kilka minut — i wywołaj je ze zwykłej strony internetowej. Twoja baza danych jest Twoim backendem: bez frameworka, bez szablonowego kodu, bez biblioteki JavaScript. Zbudujemy krok po kroku jeden „Hello World": funkcję z parametrem, powiadomienie w czasie rzeczywistym i wygenerowany plik.
Cały kod tego przewodnika jest dostępny jako gotowy przykład:
hello_world.sql (baza danych) i
index.html (strona internetowa).
Każdy endpoint jest szczegółowo opisany na własnej stronie referencyjnej: JSON-RPC,
Powiadomienia w czasie rzeczywistym (SSE) i Pobieranie plików (/file).
1. Instalacja
Pobierz plik binarny
# macOS (Homebrew)
brew install heptau/tap/pgarachne
# or download the latest release for your OS:
# https://github.com/heptau/pgarachne/releases/latest2. Przygotuj bazę danych
Utwórz bazę danych, rolę serwisową, jako którą łączy się PgArachne, i wczytaj schemat PgArachne
(sql/schema.sql znajduje się w repozytorium):
createdb my_database
psql -d my_database -c "CREATE ROLE pgarachne LOGIN PASSWORD 'pgarachne_password'"
psql -d my_database -f sql/schema.sql3. Skonfiguruj i uruchom
Utwórz plik pgarachne.env (serwer znajdzie go w bieżącym katalogu).
Dzięki STATIC_FILES_PATH PgArachne udostępni także stronę internetową, którą utworzysz w kroku 5, więc strona i
API mają jedno źródło (origin) i nie jest potrzebna konfiguracja 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=… Uruchom serwer (hasło roli serwisowej jest odczytywane z PGPASSWORD lub ~/.pgpass):
PGPASSWORD=pgarachne_password ./pgarachne4. Hello World — funkcja z parametrem
Każda metoda API to funkcja PostgreSQL z jednym argumentem jsonb. Utwórz rolę dla strony internetowej i
funkcję; obiekt JSON, który klient wysyła jako params, trafia do funkcji jako 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;Dostępem steruje sam PostgreSQL: demo_user może wywoływać dokładnie te funkcje, do których otrzymał uprawnienie
EXECUTE — nic więcej nie jest osiągalne.
5. Wywołaj ją — z curl i ze strony internetowej
Twój punkt końcowy API już działa. Nazwa użytkownika i hasło roli PostgreSQL są wysyłane za pomocą uwierzytelniania 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}To samo wywołanie z przeglądarki, za pomocą wbudowanego fetch(). Zapisz poniższy kod jako index.html w folderze
z STATIC_FILES_PATH i otwórz 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. Czas rzeczywisty — niech baza danych wyśle wiadomość (SSE)
PostgreSQL może publikować wiadomości za pomocą pg_notify; PgArachne przekazuje je do przeglądarek jako Server-Sent Events.
Rozszerz funkcję: gdy wywołujący przekaże "notify": true, funkcja dodatkowo opublikuje powitanie
na kanale 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; $$;Na stronie najpierw zasubskrybuj kanał, a potem wywołaj funkcję. Wbudowany EventSource nie potrafi wysłać
nagłówka Authorization, dlatego strumień jest odczytywany przez fetch() (około 15 linii):
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 jest transakcyjne — PostgreSQL dostarcza je dopiero po zatwierdzeniu
transakcji funkcji, czyli dokładnie w chwili zwrócenia odpowiedzi. Subskrybuj przed wywołaniem: PostgreSQL nie przechowuje
powiadomień dla nasłuchujących, którzy nie są jeszcze połączeni. Kanały SSE nie są ograniczone do roli
(zobacz Powiadomienia w czasie rzeczywistym (SSE)).7. Pobierz plik
Funkcję zwracającą wiersze (path, content, mime_type, store_only) można pobrać przez punkt końcowy
/file — jako surowe bajty, bez base64 wewnątrz JSON. Ta zwraca plik 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;Na stronie możesz wyświetlić zawartość lub zaoferować ją do pobrania (zwróć więcej wierszy, a automatycznie otrzymasz 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);Wypróbuj z 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.
Więcej w Pobieranie plików (/file).
Zanim przejdziesz na produkcję
- Używaj HTTPS. Dane uwierzytelniające Basic są wysyłane z każdym żądaniem; zobacz Wdrożenie i HTTPS. Zmień hasła z przykładu.
- Trzymaj dane uwierzytelniające tylko w pamięci (zmienna JavaScript), nigdy w
localStorage. Aby nie wysyłać hasła za każdym razem, ustawJWT_SECRETi pobierz token zPOST /db/my_database/token(zobacz Bezpieczeństwo i uwierzytelnianie). - Strona internetowa na innym origin (CDN, inny port)? Ustaw
ALLOWED_ORIGINS=https://app.example.com; żądania cross-origin są domyślnie blokowane. - Najmniejsze uprawnienia: jedna rola PostgreSQL na rodzaj użytkownika,
EXECUTEtylko na funkcjach, których potrzebują, Row-Level Security dla danych poszczególnych użytkowników orazALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC, aby nowe funkcje nie były udostępniane przypadkowo.
8. Podłącz agenta AI (MCP)
Udostępnij te same funkcje jako narzędzia dla Claude Desktop lub Cursor przez punkt końcowy MCP:
POST http://localhost:8080/db/my_database/mcpPełne instrukcje połączenia znajdziesz w Model Context Protocol (MCP).
9. Pobierz specyfikację OpenAPI (Swagger, Postman, generowanie kodu)
Każda udostępniona funkcja jest też opisana w dokumencie OpenAPI 3.1, przefiltrowanym do tego, co może wywołać uwierzytelniony użytkownik:
curl http://localhost:8080/db/my_database/openapi.json \
-u demo_user:user_password
# or as YAML: .../openapi.yamlZaimportuj adres URL bezpośrednio do Swagger UI, Postmana lub generatora kodu OpenAPI. Każda metoda nadal przechodzi przez jeden punkt końcowy JSON-RPC z kroku 5 — w Decyzjach architektonicznych wyjaśniamy, dlaczego specyfikacja wymienia ścieżkę dla każdej metody bez odpowiadającej jej rzeczywistej trasy.
To wszystko. Przeczytaj JSON-RPC, aby poznać format żądań, pisanie funkcji, błędy i idempotencję, lub porównanie z PostgREST, aby zrozumieć, gdzie plasuje się PgArachne.