{"id":16447310,"url":"https://github.com/pramsey/pg_eventserv","last_synced_at":"2025-10-27T06:30:39.464Z","repository":{"id":61627649,"uuid":"536256828","full_name":"pramsey/pg_eventserv","owner":"pramsey","description":"Small golang server to push PgSQL listen/notify events into websockets","archived":false,"fork":false,"pushed_at":"2022-10-07T19:54:04.000Z","size":197,"stargazers_count":8,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-01T03:11:18.744Z","etag":null,"topics":["golang","postgresql","websocket"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pramsey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-09-13T18:20:23.000Z","updated_at":"2024-08-28T13:48:37.000Z","dependencies_parsed_at":"2022-10-18T17:45:19.010Z","dependency_job_id":null,"html_url":"https://github.com/pramsey/pg_eventserv","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pramsey%2Fpg_eventserv","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pramsey%2Fpg_eventserv/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pramsey%2Fpg_eventserv/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pramsey%2Fpg_eventserv/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pramsey","download_url":"https://codeload.github.com/pramsey/pg_eventserv/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238445936,"owners_count":19473842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","postgresql","websocket"],"created_at":"2024-10-11T09:50:22.200Z","updated_at":"2025-10-27T06:30:39.080Z","avatar_url":"https://github.com/pramsey.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pg_eventserv\n\nA [PostgreSQL](https://postgis.net/)-only event server in [Go](https://golang.org/). Does one thing and one thing only: take events generated by the PostgreSQL `NOTIFY` command and passes the payload along to waiting WebSockets clients.\n\n# Setup and Installation\n\n## Download\n\nBuilds of the latest code:\n\n* [Linux](https://postgisftw.s3.amazonaws.com/pg_eventserv_latest_linux.zip)\n* [Windows](https://postgisftw.s3.amazonaws.com/pg_eventserv_latest_windows.zip)\n* [MacOS](https://postgisftw.s3.amazonaws.com/pg_eventserv_latest_macos.zip)\n* [Docker](https://hub.docker.com/r/pramsey/pg_eventserv)\n\n### Source\n\nDownload the source code and build:\n```\nmake build\n```\n\n## Basic Operation\n\nThe executable will read user/connection information from the `DATABASE_URL` environment variable and connect to the database, allowing any client with HTTP access to the server to connect and set up a WebSocket listening to any allowed channel on the server.\n\n### Linux/MacOS\n\n```sh\nexport DATABASE_URL=postgresql://username:password@host/dbname\n./pg_eventserv\n```\n\n### Windows\n\n```\nSET DATABASE_URL=postgresql://username:password@host/dbname\npg_eventserv.exe\n```\n\n### Client Side\n\nOnce the service is running, you need a client to attach a web socket to it. You can use the built-in viewer at `http://localhost:7700/` for testing, but you will eventually need to build one into your client application.\n\nHere is a very simple Javascript client, for example.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003cbody\u003e\n    \u003cp\u003e\u003ctextarea id=\"display\" rows=\"20\" cols=\"60\"\u003e\u003c/textarea\u003e\u003c/p\u003e\n    \u003cp id=\"status\"\u003e\u003c/p\u003e\n    \u003cscript\u003e\n      window.onload = function() {\n        // events on channel 'people'\n        var url = \"ws://localhost:7700/listen/people\";\n        var status = document.getElementById(\"status\");\n        var display = document.getElementById(\"display\");\n        // open socket and connect event handlers\n        var ws = new WebSocket(url);\n        ws.onopen = function() {\n            status.innerHTML = \"Socket open.\";\n        };\n        ws.onerror = function(error) {\n            status.innerHTML = \"Socket error.\";\n        };\n        ws.onmessage = function (e) {\n          // First try to parse message as JSON.\n          // Catch failures and return.\n          try {\n            var payload = JSON.parse(e.data);\n            display.innerHTML += JSON.stringify(payload, null, 2) + \"\\n\";\n          }\n          catch (err) {\n            display.innerHTML += e.data + \"\\n\";\n          }\n          display.scrollTop = display.scrollHeight;\n        };\n        ws.onclose = function(event) {\n            status.innerHTML = \"Socket closed.\";\n        }\n      }\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nYou can also test the service by pointing your browser at the service test page, `http://localhost:7700/`, and entering one or more channel names.\n\n### Raising a Notification\n\nTo send a message from the database to the web socket client, connect to your database. Then run the `NOTIFY` command:\n\n```sql\nNOTIFY channelname, 'message to send';\n```\n\nYou can also raise a notification by running the `pg_notify()` function.\n\n```sql\nSELECT pg_notify('channelname', 'message to send');\n```\n\n\n## Trouble-shooting\n\nTo get more information about what is going on behind the scenes, run with the `--debug` commandline parameter on, or turn on debugging in the configuration file:\n```sh\n./pg_eventserv --debug\n```\n\n### Configuration Using Environment Variables\n\nAny parameter in the configuration file can be over-ridden at run-time in the environment. Prepend the upper-cased parameter name with `ES_` to set the value. For example, to change the HTTP port using the environment:\n```bash\nexport ES_HTTPPORT=7777\n```\n\n\n# Operation\n\nThe purpose of `pg_eventserv` is to take events that are generated in the database and make them accessible to web clients. The general idea is to instrument the database model to capture the events that are of interest to your application, so you don't need to build that orchestration somewhere else. Databases have lots of logic and filtering smarts, and generating the events of interests inside the database can simplify development of event-driven applications.\n\n## Listening to a Channel\n\nYou can listen to any channel allowed by the service (the default is to open all channels, but that can be limited with the `Channels` configuration option) by opening a WebSocket connection with the following URL pattern.\n\n```\nws://{host}:7700/listen/{channel}\n```\nOnce the channel is open all `NOTIFY` commands in the database that reference the channel will cause a WebSockets message to be sent to all clients listening to the channel.\n\n## Simple Data Notification\n\nThe most basic form of event is a change of data. An insert or an update, for example. Here's an example that generates a new event for every insert and update on the table.\n\n```sql\nCREATE TABLE people (\n    pk serial primary key,\n    ts timestamptz DEFAULT now(),\n    name text,\n    age integer,\n    height real\n);\n\nCREATE OR REPLACE FUNCTION data_change() RETURNS trigger AS\n$$\n    DECLARE\n        js jsonb;\n    BEGIN\n        SELECT to_jsonb(NEW.*) INTO js;\n        js := jsonb_set(js, '{dml_action}', to_jsonb(TG_OP));\n        PERFORM (\n            SELECT pg_notify('people', js::text)\n        );\n        RETURN NEW;\n    END;\n$$ LANGUAGE 'plpgsql';\n\nCREATE OR REPLACE TRIGGER data_change_trigger\n    BEFORE INSERT OR UPDATE ON people\n    FOR EACH ROW\n        EXECUTE FUNCTION data_change();\n```\n\nInstall these functions, turn on a web socket for the `people` channel (use the service front page, for example) then run some data modifications.\n\n```sql\nINSERT INTO people (name, age, height) VALUES ('Paul', 51, 1.9);\nINSERT INTO people (name, age, height) VALUES ('Colin', 65, 1.5);\n```\n\n## Filtered Data Notification\n\nSending data modification events is less interesting than sending events when some kind of condition exists. For example, we might only want to raise events when the new data indicates a `height` greater than `2.0`.\n\n```sql\nCREATE OR REPLACE FUNCTION data_change() RETURNS trigger AS\n$$\n    DECLARE\n        js jsonb;\n    BEGIN\n        IF NEW.height \u003e= 2.0\n        THEN\n            SELECT to_jsonb(NEW.*) INTO js;\n            PERFORM (\n                SELECT pg_notify('people', js::text)\n            );\n        END IF;\n        RETURN NEW;\n    END;\n$$ LANGUAGE 'plpgsql';\n```\n\nThen send in some updates that pass the filter and others that don't.\n\n```sql\nUPDATE people SET name = 'Shorty', height = 1.5 WHERE age = 51;\nUPDATE people SET name = 'Bozo', height = 2.1 WHERE age = 51;\nINSERT INTO people (name, age, height) VALUES ('Stretch', 33, 2.8);\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpramsey%2Fpg_eventserv","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpramsey%2Fpg_eventserv","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpramsey%2Fpg_eventserv/lists"}