Γρήγορη εκκίνηση
Γρήγορη εκκίνηση
Αποκτήστε ένα λειτουργικό API πάνω από τη βάση δεδομένων PostgreSQL σας σε λίγα λεπτά — και καλέστε το από μια απλή ιστοσελίδα. Η βάση δεδομένων σας είναι το backend σας: χωρίς framework, χωρίς περιττό κώδικα, χωρίς βιβλιοθήκη 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 το παραδίδει όταν γίνεται commit
η συναλλαγή της συνάρτησης, δηλαδή ακριβώς τη στιγμή που επιστρέφει η κλήση. Εγγραφείτε πριν καλέσετε: η PostgreSQL δεν διατηρεί
ειδοποιήσεις για ακροατές που δεν έχουν συνδεθεί ακόμη. Τα κανάλια SSE δεν περιορίζονται ανά ρόλο
(δείτε Ειδοποιήσεις σε Πραγματικό Χρόνο (SSE)).7. Λήψη αρχείου
Μια συνάρτηση που επιστρέφει γραμμές (path, content, mime_type, store_only) μπορεί να ληφθεί μέσω του τελικού σημείου
/file — ως ακατέργαστα bytes, χωρίς 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 ταξιδεύουν με κάθε αίτημα· δείτε Deployment & HTTPS. Αλλάξτε τους κωδικούς του παραδείγματος.
- Κρατήστε τα διαπιστευτήρια μόνο στη μνήμη (μια μεταβλητή JavaScript), ποτέ στο
localStorage. Για να μην στέλνετε ξανά τον κωδικό, ορίστεJWT_SECRETκαι ζητήστε token από τοPOST /db/my_database/token(δείτε Ασφάλεια & Πιστοποίηση). - Ιστοσελίδα σε άλλο origin (CDN, άλλη θύρα); Ορίστε
ALLOWED_ORIGINS=https://app.example.com· τα cross-origin αιτήματα αποκλείονται από προεπιλογή. - Ελάχιστα προνόμια: ένας ρόλος PostgreSQL ανά είδος χρήστη,
EXECUTEμόνο στις συναρτήσεις που χρειάζονται, Row-Level Security για δεδομένα ανά χρήστη καιALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC, ώστε οι νέες συναρτήσεις να μην εκτίθενται κατά λάθος.
8. Σύνδεση πράκτορα AI (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.