{"id":27297973,"url":"https://github.com/half-6/pg-api","last_synced_at":"2026-05-06T18:37:04.411Z","repository":{"id":47802427,"uuid":"87852838","full_name":"half-6/pg-api","owner":"half-6","description":"RESTful API for PostgreSQL","archived":false,"fork":false,"pushed_at":"2023-01-23T17:57:24.000Z","size":265,"stargazers_count":2,"open_issues_count":5,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-31T21:01:57.314Z","etag":null,"topics":["json-query","nodejs","postgresql","restful-api"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/half-6.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-10T20:07:27.000Z","updated_at":"2021-06-20T07:58:00.000Z","dependencies_parsed_at":"2023-02-13T01:45:35.068Z","dependency_job_id":null,"html_url":"https://github.com/half-6/pg-api","commit_stats":null,"previous_names":["cyokin/pg-api"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/half-6%2Fpg-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/half-6%2Fpg-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/half-6%2Fpg-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/half-6%2Fpg-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/half-6","download_url":"https://codeload.github.com/half-6/pg-api/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248441672,"owners_count":21104046,"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":["json-query","nodejs","postgresql","restful-api"],"created_at":"2025-04-12T00:25:09.866Z","updated_at":"2026-05-06T18:36:59.388Z","avatar_url":"https://github.com/half-6.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PG-API\n\u003cspan class=\"badge-npmversion\"\u003e\u003ca href=\"https://npmjs.org/package/@linkfuture/pg-api\" title=\"View this project on NPM\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/@linkfuture/pg-api.svg\" alt=\"NPM version\" /\u003e\u003c/a\u003e\u003c/span\u003e\n\u003cspan class=\"badge-npmdownloads\"\u003e\u003ca href=\"https://npmjs.org/package/@linkfuture/pg-api\" title=\"NPM downloads\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/@linkfuture/pg-api.svg\" alt=\"NPM downloads\" /\u003e\u003c/a\u003e\u003c/span\u003e\n\u003cspan class=\"badge-npmstatus\"\u003e\u003ca href=\"https://circleci.com/gh/half-6/@linkfuture/pg-api\" title=\"NPM Status\"\u003e\u003cimg src=\"https://img.shields.io/circleci/project/github/half-6/pg-api.svg\" alt=\"NPM Status\" /\u003e\u003c/a\u003e\u003c/span\u003e\n\u003cspan class=\"badge-npmlicense\"\u003e\u003ca href=\"#license\" title=\"License\"\u003e\u003cimg src=\"https://img.shields.io/npm/l/@linkfuture/pg-api.svg?style=flat-square\" alt=\"License\" /\u003e\u003c/a\u003e\u003c/span\u003e\n\nRESTful API for PostgreSQL  \n\u003cspan style=\"color:gray; font-size: 10px;\"\u003eAn easier way to query database\u003c/span\u003e\n\nTable of Contents\n- [Installation](#installation)\n- [Usage](#usage)\n- [Query](#query)\n- [Query Composite](#composite)\n- [Query Enum](#enum)\n- [Configuration](#configuration)\n- [Events](#events) \n- [KeyWords](#keywords)\n- [Notice](#notice)\n\n## Installation\n\nThe easiest way to install @linkfuture/pg-api is with [`npm`][npm].\n\n[npm]: https://www.npmjs.com/\n\n```sh\nnpm install @linkfuture/pg-api\n```\n\n## Usage\n\n``` js\n//reference: https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string\nconst $config = {\n    //\"connection\":\"postgres://\u003cuser\u003e:\u003cpassword\u003e@\u003chost\u003e:\u003cport\u003e/\u003cdatabase\u003e?ssl=true\u0026sslmode=no-verify\",\n    \"connection\":{\n\t  \"user\": \"postgres\",\n      \"host\": \"\u003chostname\u003e\",\n      \"database\": \"\u003cdatabase\u003e\",\n      \"password\": \"\u003cpassword\u003e\",\n      \"port\": 5432,\n      \"ssl\": { rejectUnauthorized: false }\n\t},\n    \"tables\":{\n        \"user\":{\n            delete:false,//disable delete operation on user table, other operation will be available as default. \n         }\n    }\n}\nconst $pgConnector = require(\"@linkfuture/pg-api\");\n// access through Restful API\nconst $pgApi = $pgConnector.api($config);\napp.use(\"/api/db/\",$pgApi);  \n// access through NodeJs\nconst $pgQuery = $pgConnector.query($config);\nlet result = await $pgQuery.select(\"user\",{$where:{user_id:1}}); \n```\n\n## Env Support\n``` js\nPGUSER=dbuser\nPGHOST=database.server.com\nPGPASSWORD=secretpassword\nPGDATABASE=mydb\nPGPORT=3211\n```\n\n## Query\n\n### SELECT (GET)\n- Select by Primary Key\n``` HTTP\n    GET http://[host]/api/db/[table-name or view-name]/[id]\n    GET http://[host]/api/db/user/1\n``` \n\n- Select by JSON Query\n``` HTTP\n    GET http://[host]/api/db/[table-name or view-name]?$q=[JSON QUERY]\n    GET http://[host]/api/db/user?$q={\"$where\":{\"id\":{\"$any\":[1,2,3]}}}\n``` \n\n- Select by Query String\n``` HTTP\n    GET http://[host]/api/db/[table-name or view-name]?[ColumnName]=[ColumnValue]\u0026$limit=10\n    GET http://[host]/api/db/user?age={\"$gt\":5,\"$lt\":50}\u0026is_active=1\u0026$limit=1\n``` \n\n- Select in Node\n``` js\n    const $pgQuery = $pgConnector.query($config);\n    let result = await $pgQuery.select(\"user\",{$where:{user_id:1}});\n    let result = await $pgQuery.selectOne(\"user\",{$where:{user_id:1}});\n    let result = await $pgQuery.selectById(\"user\",1);\n``` \n\n- JSON Query Example  \n  Normal Query\n``` js\n{\n  \"*\":true\n  ,\"unknown_field2\": {\"$multiply\":[\"age\",\"price\",\"price\"]}\n  ,\"unknown_field3\": {\"$multiply\":[\"age\",{\"$divide\":[\"age\",\"price\"]}]}\n  ,\"unknown_field4\": {\"$divide\":[\"age\",\"price\"]}\n  ,\"unknown_field5\": {\"$plus\":[\"age\",\"price\"]}\n  ,\"unknown_field6\": {\"$minus\":[\"age\",\"price\"]}\n  ,\"unknown_field7\": {\"$module\":[\"age\",\"price\"]}\n  ,\"$where\":{\n      \"display_name\": \"UNIT TEST\",\n      \"account\": {\"$similar\":\"account%\"},\n      \"age\":{\"$gt\":5,\"$lt\":50},\n      \"is_active\":1,\n      \"roles\":[1,2],\n      \"price\":{\"$between\":[300,500]},\n      \"account_id\":{\"$any\": [4,3]},\n      \"meta\":{\"$contain\":{\"b\":4}},\n      \"$or\":[\n          { \"account\":\"test_1\"},\n          { \"account\":\"test_2\"}\n       ]\n  }\n  ,\"$sort\":{\"data_registered\":\"DESC\",\"account_id\":\"ASC\"  }\n  ,\"$limit\":10\n  ,\"$offset\":0\n}\n```\n   Group by Query\n``` js\n{\n  \"gender\":true\n  ,\"sum_operation\": {\"$sum\":\"age\"}\n  ,\"sum_multiply_operation\": {\"$sum\":{\"$multiply\":[\"age\",\"price\"]}}\n  ,\"count_operation\": {\"$count\":1}\n  ,\"min_operation\": {\"$min\":\"age\"}\n  ,\"max_operation\": {\"$max\":\"age\"}\n  ,\"avg_operation\": {\"$avg\":\"age\"}\n  ,\"$where\":{\n    \"display_name\": {\"$like\":\"% display %\"}\n    ,\"data_registered\":{\"$gt\":\"2015-09-30 21:21:31.647424+00\"}\n    ,\"$or\":[\n        { \"account\":\"account_1\"}\n      ,{ \"account\":\"account_2\"}\n      ,{\"display_name\": {\"$similar\":\"my display name\"}}\n    ]\n  }\n  ,\"$limit\":10\n  ,\"$offset\":0\n  ,\"$group\":[\"gender\",\"age\"]\n}\n```\n  Distinct Query\n``` js\n{\n  \"$distinct\":[\"price\",{\"unknown_field2\":{\"$multiply\":[\"age\",\"price\",\"price\"]}}]\n  ,\"$limit\":10\n  ,\"$offset\":0\n}\n```\n\n### INSERT (POST)\n- INSERT by JSON Query\n``` HTTP\n    POST http://[host]/api/db/[table-name]\n    POST http://[host]/api/db/user\n    {\n         \"account\":\"my_account_1\"\n        ,\"password\":\"my passowrd\"\n        ,\"display_name\":\"my display name\"\n        ,\"gender\":\"male\"\n        ,\"date_registered\":\"2015-10-30 14:21:31.647424 -07:00:00\"\n        ,\"struct\":{\"name\":\"full update\",\"supplier_id\":[10,50],\"price\":1.99} \n        ,\"age\":10\n        ,\"price\":50\n        ,\"roles\":[1,2]\n        ,\"is_active\":true\n        ,\"struct\":null\n        ,\"meta\":null\n      }\n``` \n- BULK INSERT by JSON Query\n``` HTTP\n    POST http://[host]/api/db/[table-name]\n    POST http://[host]/api/db/user\n    [\n      {\n         \"account\":\"my_account_1\"\n        ,\"password\":\"my passowrd\"\n        ,\"display_name\":\"my display name\"\n        ,\"gender\":\"male\"\n        ,\"date_registered\":\"2015-10-30 14:21:31.647424 -07:00:00\"\n        ,\"age\":10\n        ,\"price\":50\n        ,\"roles\":[1,2]\n        ,\"is_active\":true\n        ,\"struct\":null\n        ,\"meta\":null\n      }\n      ,{\n         \"account\":\"my_account_2\"\n        ,\"password\":\"my passowrd\"\n        ,\"gender\":\"female\"\n        ,\"price\":50\n        ,\"age\":10\n        ,\"display_name\":\"my display name\"\n        ,\"date_registered\":\"2015-10-30 14:21:31.647424 -07:00:00\"\n        ,\"meta\":\n        {\n          \"img\":\"https://scontent-ord1-1.xx.fbcdn.net/v/t1.0-1/c9.0.40.40/p40x40/1618502_10203352692842640_430525865_n.jpg?oh=10b7e45293509d2b667a27f21985891f\u0026oe=582C74C9\"\n        ,\"gender\":\"male\"\n        ,\"languages\":\"english\"\n        }\n      }\n    ]\n``` \n\n- Insert in Node\n``` js\n    const $pgQuery = $pgConnector.query($config);\n    let result = await $pgQuery.insert(\"user\",[{\"account\":\"my_account_1\",,\"password\":\"my passowrd\"}}]);\n``` \n\n### UPSERT (PUT)\nThe UPDATE action to be performed in case of a conflict,otherwise do insert.  \nReference https://www.postgresql.org/docs/9.5/static/sql-insert.html for more\n- UPSERT by JSON Query\n``` HTTP\n    PUT http://[host]/api/db/[table-name]/[constraint_name]\n    PUT http://[host]/api/db/city/city_pkey\n    {\n        \"id\":1  //update if id is exist\n        ,\"name\":\"my_account_2\"\n        ,\"district\":\"11213\"\n        ,\"countrycode\":\"my passowrd\"\n        ,\"population\":6000\n    }\n```  \n    \n- BULK INSERT by JSON Query\n``` HTTP\n    PUT http://[host]/api/db/[table-name]/[constraint_name]\n    PUT http://[host]/api/db/city/city_pkey\n    [\n      {\n        \"id\":1 //update if id is exist\n        ,\"name\":\"my_account_2\" \n        ,\"countrycode\":\"my passowrd\"\n        ,\"district\":\"11213\"\n        ,\"population\":6000\n      },\n      {\n        \"id\":2 //update if id is exist\n        ,\"name\":\"my_account_1\"\n        ,\"countrycode\":\"my passowrd\"\n        ,\"district\":\"22222\"\n        ,\"population\":7000\n      }\n    ]\n```  \n\n- Upsert in Node\n``` js\n    const $pgQuery = $pgConnector.query($config);\n    let result = await $pgQuery.upsert(\"user\",[{\"account\":\"my_account_1\",,\"password\":\"my passowrd\"}}],\"city_pkey\");\n``` \n\n### PARTIALLY UPDATES (PATCH)\n- Update by JSON Query\n``` HTTP\n    PATCH http://[host]/api/db/[table-name]\n    PATCH http://[host]/api/db/user\n    {\n        \"display_name\":\"new name\",\n        \"age\":10,\n        \"$where\":{\n            \"id\":{\"$any\":[1,2,3]}\n        }\n    }\n``` \n\n- Update in Node\n``` js\n    const $pgQuery = $pgConnector.query($config);\n    let result = await $pgQuery.update(\"user\",{\"display_name\":\"new name\",\"$where\":{\"id\":{\"$any\":[1,2,3]}}}});\n``` \n\n### DELETE (DELETE)\n- Delete by Primary Key\n``` HTTP\n    DELETE http://[host]/api/db/[table-name or view-name]/[id]\n    DELETE http://[host]/api/db/user/1\n``` \n\n- Delete by JSON Query (no need [$where])\n``` HTTP\n    DELETE http://[host]/api/db/[table-name]?$q=[JSON QUERY]\n    DELETE http://[host]/api/db/user?$q={\"id\":{\"$any\":[1,2,3]}}\n``` \n\n- Delete by Query String\n``` HTTP\n    DELETE http://[host]/api/db/[table-name]?[ColumnName]=[ColumnValue]\n    DELETE http://[host]/api/db/user?age={\"$gt\":5,\"$lt\":50}\u0026is_active=1\n``` \n\n- Delete in Node\n``` js\n    const $pgQuery = $pgConnector.query($config);\n    let result = await $pgQuery.delete(\"user\",{\"id\":{\"$any\":[1,2,3]}});\n    let result = await $pgQuery.deleteById(\"user\",1);\n``` \n\n## Composite  \nQuery composite \n``` HTTP\n    GET http://[host]/api/db/composite/[composite name]\n    GET http://[host]/api/db/composite/type_struct\n``` \n\n## Enum  \nQuery enum \n``` HTTP\n    GET http://[host]/api/db/enum/[enum name]\n    GET http://[host]/api/db/enum/type_gender\n``` \n\n## Function (GET or POST)\nQuery Function, you can pass params with specific arguments sequence or pass with object\n``` HTTP\n    GET http://[host]/api/func/[func name]?$params=\u003cparameters\u003e\n    GET http://[host]/api/func/f_table?$params=1\u0026$params=999\n    GET http://[host]/api/func/f_table?_user_id=1\u0026_company_id=999\n    //auto apply \"_\" on begin for better user experience \n    GET http://[host]/api/func/f_table?user_id=1\u0026company_id=999\n    //auto add default value on function when specific on the code\n    GET http://[host]/api/func/f_table?user_id=1\n    POST http://[host]/api/func/f_table\n    {\n         \"_company_id\":999\n        ,\"_user_id\":1\n    }\n    POST http://[host]/api/func/f_table\n    {\n         \"company_id\":999\n        ,\"user_id\":1\n    }\n    POST http://[host]/api/func/f_table\n    {\n        \"user_id\":1\n    }\n    SELECT * from f_table(1,999)\n    CREATE FUNCTION f_table (\n        _user_id int,\n        _company_id int DEFAULT 1\n    )\n``` \n\n## Configuration\nFor security reason, sometimes you may want to disable the operation on specific table, like disable delete operation on user table. You can leverage following configuration to reslove this issue.  \nBy default, the API will enable all operations(select,delete,insert,update) on all tables and views\n``` js\nconst $config = {\n    \"connection\":\"postgres://\u003cuser\u003e:\u003cpassword\u003e@\u003chost\u003e:\u003cport\u003e/\u003cdbname\u003e\",\n    \"tables\":{\n        \"user\":{ \n            select:true,\n            delete:false,//disable delete operation on user table\n            update:false, //disable update operation on user table\n            insert:false, //disable insert operation on user table\n            max_limit:5000, //set default select max results, by default is 1000\n            limit:12 //set default select limit, by default is 10\n         }\n    },\n    \"composites\":{\n        \"type_struct\":false //disable type_struct composite query, \n    },\n    \"enum\":{\n        \"type_gender\":false //disable type_gender enum query, \n    },\n    \"functions\":{\n        \"f_check_error\":false //disable f_check_error function query, \n    },\n    \"events\":{\n        async onRequest:function () {\n            $logger.info(\"onRequest =\u003e\",JSON.stringify(arguments));\n        },\n        async on_select_city_request:function () {\n            $logger.info(\"on_select_city_request =\u003e\",JSON.stringify(arguments));\n        },\n    }\n    \"custom\":{ //custom query, you can define your own script with transaction \n            \"find-user\":{\n                \"query\":[\n                    \"select * from public.user where account_id=${id};\",\n                    \"select * from public.city where id=${cityId}\",\n                    \"insert into public.user(account,display_name) VALUES(${name1},${display_name1}),(${name2},${display_name2}) returning account_id\",\n                    \"update public.user set display_name = ${updated_display_name} where account=${name1}\",\n                    \"delete from public.user where account=${deletename}\",\n                ],\n                \"method\":[\"GET\",\"post\"]\n            }\n     }\n}\n``` \n\n## Events\nEvents life cycle, you can catch on either global level (i.e onRequest) or specific action level(i.e on_select_city_request).  \nRequest =\u003e Build =\u003e Query =\u003e Complete\n- Request: when api load\n- Build: before build TSQL and verify column and parameters\n- Query: before db operation\n- Complete: after DB operation\n\n``` js\n        \"events\":{\n            async onRequest:function () {\n                $logger.info(\"onRequest =\u003e\",JSON.stringify(arguments));\n            },\n            async on_city_request:function () {\n                $logger.info(\"on_city_request =\u003e\",JSON.stringify(arguments));\n            },\n            async on_select_city_request:function () {\n                $logger.info(\"on_select_city_request =\u003e\",JSON.stringify(arguments));\n            },\n            async onBuild:function () {\n                $logger.info(\"onBuild =\u003e\",JSON.stringify(arguments));\n            },\n            async on_city_build:function () {\n                $logger.info(\"on_city_build =\u003e\",JSON.stringify(arguments));\n            },\n            async on_select_city_build:function () {\n                $logger.info(\"on_select_city_build =\u003e\",JSON.stringify(arguments));\n            },\n            async onQuery:function () {\n                $logger.info(\"onQuery =\u003e\",JSON.stringify(arguments));\n            },\n            async on_city_query:function () {\n                $logger.info(\"on_city_query =\u003e\",JSON.stringify(arguments));\n            },            \n            async on_select_city_query:function () {\n                $logger.info(\"on_select_city_query =\u003e\",JSON.stringify(arguments));\n            },\n            async onComplete:function () {\n                $logger.info(\"onComplete =\u003e\",JSON.stringify(arguments));\n            },\n            async on_city_complete:function () {\n                $logger.info(\"on_select_city_complete =\u003e\",JSON.stringify(arguments));\n            },            \n            async on_select_city_complete:function () {\n                $logger.info(\"on_select_city_complete =\u003e\",JSON.stringify(arguments));\n            },\n            async on_delete_city_complete:function () {\n                $logger.info(\"on_delete_city_complete =\u003e\",JSON.stringify(arguments));\n            },\n        },\n``` \n\n## pg repositories  \n``` js\nconst $pgConnector = require(\"@linkfuture/pg-api\");\nconst $repository = await $pgConnector.repository.build(config);\n\n//if we have user table or view\n$repository.tables.user.select(\u003cjson query\u003e);\n$repository.tables.user.insert(\u003cjson query\u003e);\n\n//if we have type_struct composite\n$repository.composites.type_struct\n\n//if we have type_gender composite\n$repository.enums.type_gender\n```\n\n## KeyWords\n- $q \n- $or \n- $where\n- $sort\n- $limit\n- $offset\n- $group\n- $gt\n- $gte\n- $lt\n- $lte\n- $ne\n- $like\n- $ilike\n- $similar\n- $contain\n- $any\n- $between\n- $in\n- $multiply\n- $divide\n- $plus\n- $minus\n- $module\n- $sum\n- $count\n- $min\n- $max\n- $avg\n- $distinct\n- $disableCount  \n   disable select count for pagination in order to improve select performance, default is false.\n## Notice\n- Column name\n  it will be ignore if the column name does not exist in current table, but the query will be continue.\n    \n- Bit\n  Bool type will auto convert to bit if the column type is bit.   \n\n- Node 7+ Only\n  The library is writen on Node 7+, heavily use await/async. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhalf-6%2Fpg-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhalf-6%2Fpg-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhalf-6%2Fpg-api/lists"}