{"id":20038784,"url":"https://github.com/defra/hapi-pg-rest-api","last_synced_at":"2025-05-05T07:32:24.613Z","repository":{"id":28242247,"uuid":"116973497","full_name":"DEFRA/hapi-pg-rest-api","owner":"DEFRA","description":"Generates a REST API connected to a PostgreSQL database table for use in a HAPI application","archived":false,"fork":false,"pushed_at":"2024-10-17T21:20:22.000Z","size":1192,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-10-20T08:13:35.176Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DEFRA.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-10T15:13:01.000Z","updated_at":"2024-10-17T21:20:26.000Z","dependencies_parsed_at":"2023-12-27T23:22:26.135Z","dependency_job_id":"e115b333-c76e-4b8f-acdb-d1e2f0a22b27","html_url":"https://github.com/DEFRA/hapi-pg-rest-api","commit_stats":{"total_commits":126,"total_committers":9,"mean_commits":14.0,"dds":0.3492063492063492,"last_synced_commit":"88877c6683b7d2b4c0e749a5681724499f528e7e"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DEFRA%2Fhapi-pg-rest-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DEFRA%2Fhapi-pg-rest-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DEFRA%2Fhapi-pg-rest-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DEFRA%2Fhapi-pg-rest-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DEFRA","download_url":"https://codeload.github.com/DEFRA/hapi-pg-rest-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224431960,"owners_count":17310240,"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":[],"created_at":"2024-11-13T10:32:49.894Z","updated_at":"2024-11-13T10:32:50.595Z","avatar_url":"https://github.com/DEFRA.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hapi PG Rest API\n\n![Build Status](https://github.com/DEFRA/hapi-pg-rest-api/workflows/CI/badge.svg?branch=master)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=DEFRA_hapi-pg-rest-api\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=DEFRA_hapi-pg-rest-api)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=DEFRA_hapi-pg-rest-api\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=DEFRA_hapi-pg-rest-api)\n[![Known Vulnerabilities](https://snyk.io/test/github/DEFRA/hapi-pg-rest-api/badge.svg)](https://snyk.io/test/github/DEFRA/hapi-pg-rest-api)\n[![Licence](https://img.shields.io/badge/Licence-OGLv3-blue.svg)](http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3)\n\nA module to create a simple REST API in a HAPI 20 application connected to a particular Postgres DB table.\n\n## Contributing to this project\n\nPlease read the [contribution guidelines](/CONTRIBUTING.md) before submitting a pull request.\n\n## Features:\n\n* Records are identified by an auto-generated guid (by default)\n* Data is transmitted as JSON format.\n* Validation is provided by Joi.\n* A client class is also available to connect to the API created\n\nAll routes return a standard response in form:\n\n```\n{\n  \"error\": // Error response\n  \"data\": // data returned from call\n}\n```\n\nRoutes that update also return a rowCount parameter showing the number of rows modified, e.g.:\n\n```\n{\n  \"error\": null,\n  \"data\": null,\n  \"rowCount\" : 3\n}\n```\n\nWhen finding many records, pagination data is returned:\n\n```\n{\n  ...,\n  \"pagination\": {\n       \"page\": 1,\n       \"perPage\": 100,\n       \"totalRows\": 200,\n       \"pageCount\": 2\n   }\n}\n```\n\nWhen querying for the schema, JSON schema and configuration data is returned:\n\n```\n{\n  \"error\" : null,\n  \"data\" : {\n    \"jsonSchema\" : {\n      ...\n    },\n    \"config\" : {\n      \"primaryKey\" : \"field\",\n      \"primaryKeyAuto\" : false,\n      \"primaryKeyGuid\" : true\n    }\n  }\n}\n```\n\n## Usage\n\n```\nconst {Pool} = require('pg');\nconst server = new Hapi.Server();\nconst RestApi = require('rest-api');\n\n// Create a new endpoint linked to a table\nconst SessionsApi = RestApi({\n  table : 'sessions',\n  connection : pool,\n  primaryKey : 'session_id',\n  endpoint : '/api/1.0/sessions',\n  onCreateTimestamp : 'date_created',\n  onUpdateTimestamp : 'date_updated',\n  validation : {\n    session_id : Joi.string().guid(),\n    ip : Joi.string(),\n    session_data : Joi.string(),\n    date_created : Joi.string(),\n    date_updated : Joi.string().allow(null)\n  }\n});\n\n// Import routes to HAPI\nserver.route([\n  ...SessionsApi.getRoutes()\n]);\n\n// Or, import individual routes as required\nserver.route([\n  SessionsApi.findManyRoute(),\n  SessionsApi.findOneRoute(),\n  SessionsApi.createRoute(),\n  SessionsApi.updateOneRoute(),\n  SessionsApi.replaceOneRoute(),\n  SessionsApi.deleteOneRoute(),\n  SessionsApi.updateManyRoute(),\n  SessionsApi.schemaDefinitionRoute(),\n]);\n```\n\n## Configuration Options\n\n* `table` : the PostGres table to connect to\n* `connection` : the pool connection instance created with pg module\n* `primaryKey` : the primary key field in the database table (must accept string GUID)\n* `endpoint` : the base URL endpoint upon which the below calls are mounted\n* `onCreateTimestamp` : a field which will be updated when the record is created\n* `onUpdateTimestamp` : a field which will be updated when the record is updated\n* `validation` : an object containing Joi validation for the entity (required)\n* `preUpdate` : a function which can filter the data object being updated\n* `preInsert` : a function which can filter the data object being inserted\n* `preQuery` : a function which can modify the data, filter and sort after a HAPI request has been interpreted\n* `postSelect` : a function which can modify data retrieved by select query\n* `upsert` : an object containing arrays `fields` and `set` - adds an on conflict clause to an insert\n* `primaryKeyAuto` : whether primary key field is auto-generated by the DB (default false)\n* `primaryKeyGuid` : whether to use guids for primary key fields (default true)\n* `pagination` : default pagination, specified as {page : 1, perPage : 200}\n* `showSql` : for debugging, shows the generated SQL statements\n* `maxPayloadBytes` : when posting large payloads, set this to override the HAPI default\n\n\n## Supported Endpoints\n\n### Create\n\nRequest:\n```\nPOST /endpoint\nBody:\n{\n  field : 'value',\n  field2: 'value2'\n}\n```\n\nResponse:\n```\n201 Created\nBody:\n{\n  \"error\" : null,\n  \"data\" : {\n    \"field\" : \"value\",\n    \"field2\" : \"value2\"\n  }\n}\n```\n\n### Find One\n\nRequest:\n```\nGET /endpoint/:id\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : {\n    \"field\" : \"value\",\n    \"field2\" : \"value2\"\n  }\n}\n```\n\nNot Found Response:\n```\n404 Not Found\nBody:\n{\n  \"error\" : {\n    \"name\" : \"NotFoundError\"\n  },\n  \"data\" : null\n}\n```\n\n\n### Find All\n\nRequest:\n```\nGET /endpoint\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : [{\n    \"field\" : \"value\",\n    \"field2\" : \"value2\"\n  },\n  {\n    \"field\" : \"value\",\n    \"field2\" : \"value2\"\n  }],\n  \"pagination\" : {\n    \"page\": 1,\n    \"perPage\": 100,\n    \"totalRows\": 10,\n    \"pageCount\": 2\n  }\n}\n```\n\n### Filter / Sort\n\nRequest:\n```\nGET /endpoint?filter={\"field\":\"value\"}\u0026sort={\"field\":+1,\"field2\":-1}\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : [\n  ...\n  ]\n}\n```\n\nYou can also use mongo-style operators such as $gt, $gte, $lt, $lte, $like, $ilike, for example:\n\n```\nGET /endpoint?filter={\"field\": {$ilike : \"%value\"}}\u0026sort={\"field\":+1,\"field2\":-1}\n```\n\nInternally, the [mongo-sql](https://www.npmjs.com/package/mongo-sql) library is used to build filter queries.\n\n### Pagination\n\nRequest:\n```\nGET /endpoint?pagination={\"page\": 1, \"perPage\" : 5}\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : [\n  ...\n  ],\n  \"pagination\" : {\n    \"page\": 1,\n    \"perPage\": 5,\n    \"totalRows\": 10,\n    \"pageCount\": 2\n  }\n}\n```\n\n### Update One\n\nRequest:\n```\nPATCH /endpoint/:id\nBody:\n{\n  field : 'value',\n  field2: 'value2'\n}\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : null\n}\n```\n\nNot Found Response:\n```\n404 Not Found\nBody:\n{\n  \"error\" : {\n    \"name\" : \"NotFoundError\"\n  },\n  \"data\" : null\n}\n```\n\n### Update Many\n\nRequest:\n```\nPATCH /endpoint?filter={\"key\" : \"value\"}\nBody:\n{\n  field : 'value',\n  field2: 'value2'\n}\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : null,\n  \"rowCount\" : 5    // number of rows modified\n}\n```\n\n### Delete\n\nRequest:\n```\nDELETE /endpoint/:id\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : null\n}\n```\n\nNot Found Response:\n```\n404 Not Found\nBody:\n{\n  \"error\" : {\n    \"name\" : \"NotFoundError\"\n  },\n  \"data\" : null\n}\n```\n\n### Get Schema\n\nAn endpoint is available that gets a basic JSON schema representation of the\nvalidation rules provided to Joi in the configuration object.\n\nRequest:\n```\nDELETE /endpoint/schema\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : {\n    \"jsonSchema\" : {\n      ...\n    },\n    \"config\" : {\n      ...\n    }\n  }\n}\n```\n\nCurrently supported options in the schema are:\n\nJoi validation config:\n```\n{\n  id : Joi.string().guid(),\n  field_1 : Joi.string(),\n  field_2 : Joi.number(),\n  field_3 : Joi.string().required(),\n  field_4 : Joi.string().email(),\n  field_5 : Joi.string().min(4).max(16)\n}\n```\n\nSuccess Response:\n```\n200 OK\nBody:\n{\n  \"error\" : null,\n  \"data\" : {\n    \"jsonSchema\" : {\n      \"type\" : \"object\",\n      \"title\" : \"sessions\",\n      \"properties\" : {\n        \"id\" : {\n          \"type\" : \"string\",\n          \"pattern\" : \"/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\"\n        },\n        \"field_1\" : {\n          \"type\" : \"string\"\n        },\n        \"field_2\" : {\n          \"type\" : \"number\"\n        },\n        \"field_3\" : {\n          \"type\" : \"string\"\n        },\n        \"field_4\" : {\n          \"type\" : \"string\",\n          \"format\" : \"email\"\n        },\n        \"field_5\" : {\n          \"type\" : \"string\",\n          \"minLength\" : 4,\n          \"maxLength\" : 16\n        }\n      },\n      \"required\" : [\"field_3\"]\n    },\n    \"config\" : {\n      \"primaryKey\" : \"id\",\n      \"primaryKeyAuto\" : false,\n      \"primaryKeyGuid\" : true\n    }\n  }\n}\n```\n\n\n\n\n## Validation\n\nData is validated with Joi validation, and on failure, the 'error' key in the response is populated with the Joi error.\n\nFor example:\n```\n{\n    \"data\": null,\n    \"error\": {\n        \"name\": \"ValidationError\",\n        \"isJoi\": true,\n        \"details\": [\n            {\n                \"message\": \"\\\"ip\\\" must be a string\",\n                \"path\": [\n                    \"ip\"\n                ],\n                \"type\": \"string.base\",\n                \"context\": {\n                    \"value\": 123,\n                    \"key\": \"ip\",\n                    \"label\": \"ip\"\n                }\n            }\n        ],\n        \"_object\": {\n            \"ip\": 123,\n            \"session_data\": \"{\\\"key\\\" : \\\"value\\\"}\"\n        }\n    }\n}\n```\n\n\n## API Client\n\nAn API client is also available to connect with the server API.\nIt depends on request-promise-native.\n\n```\nconst APIClient = require('hapi-pg-rest-api').APIClient;\nconst rp = require('request-promise-native');\nconst client = new APIClient(rp, {\n  endpoint : 'http://localhost/some/api/endpoint',\n  headers : {\n    Authorization : '...'\n  }\n});\n```\n\n### Client methods:\n\n```\nconst data = {field : 'value', field2 : 'value2'};\nconst filter = {field : 'value'};\nconst sort = {field2 : +1, field3 : -1};\nconst pagination = {page : 1, perPage : 5};\n\n// Single record\nvar {data, error} = await client.create(data);\nvar {data, error} = await client.findOne('guid');\nvar {data, rowCount, error} = await client.updateOne('guid', data);\nawait client.delete('guid');\n\n// Batch\nvar {data, error} = await client.findMany(filter, sort, pagination, columns);\nvar data = await client.findAll(filter, sort, pagination, columns); // Finds all pages in result set\nvar {data, rowCount, error} = await client.updateMany(filter, data);\n\n// Schema\nvar {data, error} = await client.schema();\n```\n\n### Error Handling\n\nFor errors returned by the API, e.g. Joi validation errors,  this is returned as\nnormal in the return from the call.\n\nFor other errors, e.g. those generates by the HTTP request (e.g. bad gateway),\nthe error is thrown.\nAll the client methods above throw an error if a n\n\nThis can be either an error returned by the API, e.g. a Joi validation error, or a status code error returned from request-promise-native.\n\n\n## Tests\n\nTo run the test suite\n\n* Ensure the DATABASE_URL environment variable is set to a valid Postgres DSN\n* Run `npm run migrate` to set up the test database tables\n* Run `npm run test` to run the lab test suite\n\n## License\n\nTHIS INFORMATION IS LICENSED UNDER THE CONDITIONS OF THE OPEN GOVERNMENT LICENCE found at:\n\n\u003chttp://www.nationalarchives.gov.uk/doc/open-government-licence/version/3\u003e\n\nThe following attribution statement MUST be cited in your products and applications when using this information.\n\n\u003eContains public sector information licensed under the Open Government license v3\n\n### About the license\n\nThe Open Government Licence (OGL) was developed by the Controller of Her Majesty's Stationery Office (HMSO) to enable information providers in the public sector to license the use and re-use of their information under a common open licence.\n\nIt is designed to encourage use and re-use of information freely and flexibly, with only a few conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefra%2Fhapi-pg-rest-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdefra%2Fhapi-pg-rest-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefra%2Fhapi-pg-rest-api/lists"}