{"id":24201481,"url":"https://github.com/the-forgebase/restql-ts","last_synced_at":"2026-02-07T07:33:37.402Z","repository":{"id":271681442,"uuid":"914148048","full_name":"The-ForgeBase/restql-ts","owner":"The-ForgeBase","description":"A library to convert REST requests to SQL queries","archived":false,"fork":false,"pushed_at":"2025-01-10T12:17:35.000Z","size":184,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-22T00:38:05.570Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/The-ForgeBase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-09T03:18:12.000Z","updated_at":"2025-01-10T12:17:39.000Z","dependencies_parsed_at":"2025-01-09T08:59:19.244Z","dependency_job_id":"b2afaa00-fe19-4df7-aad9-6b8659178118","html_url":"https://github.com/The-ForgeBase/restql-ts","commit_stats":null,"previous_names":["the-forgebase/restql-ts"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/The-ForgeBase/restql-ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-ForgeBase%2Frestql-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-ForgeBase%2Frestql-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-ForgeBase%2Frestql-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-ForgeBase%2Frestql-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/The-ForgeBase","download_url":"https://codeload.github.com/The-ForgeBase/restql-ts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/The-ForgeBase%2Frestql-ts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29189353,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T05:07:31.176Z","status":"ssl_error","status_checked_at":"2026-02-07T05:06:15.227Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":"2025-01-13T21:16:06.139Z","updated_at":"2026-02-07T07:33:37.368Z","avatar_url":"https://github.com/The-ForgeBase.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RestQL-TS\n\nRestQL-TS is a powerful TypeScript library that converts REST API requests into SQL queries. It provides a flexible and type-safe way to transform HTTP requests into database operations, supporting multiple SQL dialects including MySQL, PostgreSQL, and SQLite.\n\n## Features\n\n- 🚀 **REST to SQL Translation**: Automatically converts REST API requests into optimized SQL queries\n- 🎯 **Type Safety**: Full TypeScript support with comprehensive type definitions\n- 🔌 **Multiple SQL Dialects**: Supports MySQL, PostgreSQL, and SQLite\n- 🔄 **Flexible Query Building**: Advanced query capabilities including:\n  - Complex WHERE conditions with AND/OR grouping\n  - Nested conditions and NOT operators\n  - JOINs with multiple conditions and aliases\n  - GROUP BY, HAVING, ORDER BY, and pagination\n- 🌐 **Framework Agnostic**: Works with any web framework through adapters\n  - Express adapter\n  - Fastify adapter\n  - Web standard adapter\n- 🛡️ **Query Validation for adapters**: Built-in validation and sanitization for adapters\n- 🛡️ **Query Sanitization**: Built-in sanitization which includes custom sanitization and validation options (WIP)\n- 🔍 **Parameter Binding**: Secure parameter binding to prevent SQL injection\n\n## Installation\n\n```bash\nnpm install restql-ts\n# or\nyarn add restql-ts\n# or\npnpm add restql-ts\n```\n\n## Quick Start\n\n### Important Note\n\nThe `encodeQuery` and `decodeQuery` functions are used to encode and decode the query string. They are not part of the RestQL library, but they are provided for convenience.\n\n### Basic Usage\n\n```typescript\nimport { createWebAdapter } from \"restql-ts/adapters/web\";\nimport { encodeQuery, decodeQuery } from \"restql-ts\";\n\n// Create an adapter with your preferred SQL dialect\nconst adapter = createWebAdapter({\n  dialect: \"postgres\", // or 'mysql' or 'sqlite'\n});\n\n// Convert a REST request to SQL\n// The query string should be in the format of `q=encodedQuery`\n// The encoded query should be a valid JSON string\n// You can use the `encodeQuery` function to encode the query string\n// This is an example of a GET request\nconst request = new Request(\n  \"http://api.example.com/users?q=\" +\n    encodeQuery(\n      JSON.stringify({\n        select: [\"id\", \"name\", \"email\"],\n        where: {\n          operator: \"AND\",\n          conditions: [\n            { field: \"age\", operator: \"\u003e\", value: 18 },\n            { field: \"status\", operator: \"=\", value: \"active\" },\n          ],\n        },\n      })\n    )\n);\n\nconst { sql, params } = await adapter.toSQL(request);\n// SQL: SELECT \"id\", \"name\", \"email\" FROM \"users\" WHERE \"age\" \u003e $1 AND \"status\" = $2\n// Params: [18, 'active']\n```\n\n### Alternative Request Method: JSON Payload\n\nIn addition to the standard query string method, RestQL-TS offers an alternative approach for sending requests using JSON payloads via POST requests. This can be particularly useful when dealing with complex queries or when you prefer to organize your request data within a structured JSON format. This method is entirely optional and does not replace the existing query string functionality, which continues to be fully supported.\n\n#### JSON Payload Structure\n\nWhen using the JSON payload method, the request body should be structured as follows:\n\n```json\n{\n   \"action\": \"get\", // this action is only needed when using the JSON payload method for get requests i.e for select operations\n   \"query\": { // query options and parameters, such as select, where, joins, etc. }\n}\n```\n\n- **`action`**: This field indicates the desired operation or method that should be performed (this is only used for the query, not the method of the request).\n- **`query`**: This field contains the query parameters and options.\n\n### Mutations (POST/PUT/DELETE) (this works with both JSON Payload and Normal Request)\n\nFor mutations (POST/PUT/DELETE), the request body should be structured as follows:\n\n```json\n[\n  { \"id\": 1, \"status\": \"active\" },\n  { \"id\": 2, \"status\": \"inactive\" }\n]\n```\n\nFor single update requests, the request body should be structured as follows:\n\n```json\n{ \"id\": 1, \"status\": \"active\" }\n```\n\n- **`id`**: The id of the record to be updated.\n- **`status`**: The new status of the record.\n\nThe above is an example of a bulk update request.\n\nFor bulk delete requests, the request body should be structured as follows:\n\n```json\n[{ \"id\": 1 }, { \"id\": 2 }]\n```\n\nFor single delete requests, the request body should be structured as follows:\n\n```json\n{ \"id\": 1 }\n```\n\nFor single insert requests, the request body should be structured as follows:\n\n```json\n{ \"id\": 1, \"name\": \"John\", \"email\": \"john@example.com\" }\n```\n\nFor bulk insert requests, the request body should be structured as follows:\n\n```json\n[\n  { \"id\": 1, \"name\": \"John\", \"email\": \"john@example.com\" },\n  { \"id\": 2, \"name\": \"Jane\", \"email\": \"jane@example.com\" }\n]\n```\n\n### Example using JSON Payload for Mutations\n\n```typescript\nimport { createWebAdapter } from \"restql-ts/adapters/web\";\n\nconst adapter = createWebAdapter(\n  { dialect: \"postgres\" },\n  { enableJsonPayloads: true }\n);\n\nconst request = new Request(\"http://api.example.com/users\", {\n  method: \"POST\", // the method should always be POST/PUT/DELETE\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify([\n    { id: 1, status: \"active\" },\n    { id: 2, status: \"inactive\" },\n  ]),\n});\n```\n\n### Example using Normal Request for Mutations\n\n```typescript\nimport { createWebAdapter } from \"restql-ts/adapters/web\";\n\nconst adapter = createWebAdapter({ dialect: \"postgres\" });\n\nconst request = new Request(\"http://api.example.com/users\", {\n  method: \"POST\", // the method should always be POST/PUT/DELETE\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify([\n    { id: 1, status: \"active\" },\n    { id: 2, status: \"inactive\" },\n  ]),\n});\n```\n\n### Recommended Usage\n\n- **`JSON Payload`**: Use this method for complex queries or if you prefer to organize your select query in a human-readable and structured format.\n- **`Normal Request`**: Use this method for mutations.\n\n#### Example Usage (JSON Payload) for Select Queries\n\nHere's an example of how to use the JSON payload method for select queries:\n\n```typescript\nimport { createWebAdapter } from \"restql-ts/adapters/web\";\n\nconst adapter = createWebAdapter(\n  { dialect: \"postgres\" },\n  { enableJsonPayloads: true }\n);\n\n// Example of a POST request with a JSON payload const request = new\nconst request = new Request(\"http://api.example.com/users\", {\n  method: \"POST\", // the method should always be POST\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    action: \"get\",\n    query: {\n      select: [\"id\", \"name\", \"email\"],\n      where: {\n        operator: \"AND\",\n        conditions: [\n          { field: \"age\", operator: \"\u003e\", value: 18 },\n          { field: \"status\", operator: \"=\", value: \"active\" },\n        ],\n      },\n    },\n  }),\n});\n\nconst { sql, params } = await adapter.toSQL(request); // SQL: SELECT \"id\", \"name\", \"email\" FROM \"users\" WHERE \"age\" \u003e $1 AND \"status\" = $2 // Params: [18, 'active']\n```\n\nIn this example:\n\n- A POST request is made to `http://api.example.com/users`.\n- The request body is a JSON string.\n- The `action` is set to `\"get\"` to perform a select operation.\n- the query has all the other options that are desired.\n\nThis approach provides a clean and organized way to handle more complex queries, keeping the URL simple and the request body structured.\n\n#### When to use JSON Payload?\n\nYou may prefer to use the JSON payload method in the following scenarios:\n\n- **Complex Queries:** When dealing with deeply nested conditions or a large number of parameters.\n- **Improved Readability:** When you want to organize your query in a human-readable and structured format.\n- **When you want to use POST** : Some times for security reasons sending information in the URL may not be desired.\n- **Security**: When you don't want to have query params in the URL\n- **Large paylaods:** the URL can only carry limited amount of data, so to pass large payloads it is desired to use post.\n\n#### Continued Support for Query Strings\n\nRemember, the original query string method, using the `q` parameter, is still fully supported and can be used interchangeably with the JSON payload method.\n\n### Express Integration\n\n```typescript\nimport express from \"express\";\nimport { createExpressAdapter } from \"restql-ts/adapters/express\";\n\nconst app = express();\nconst adapter = createExpressAdapter({\n  dialect: \"mysql\",\n});\n\napp.get(\"/users\", async (req, res) =\u003e {\n  // The query string should have being encoded from the client side\n  // The query string should be in the format of `q=encodedQuery`\n  // The encoded query should be a valid JSON string\n  // You can use the `encodeQuery` function to encode the query string\n  const { sql, params } = await adapter.toSQL(req);\n  // Execute the query with your database client\n  // const result = await db.query(sql, params);\n  res.json(result);\n});\n```\n\n### Fastify Integration\n\n```typescript\nimport { createFastifyAdapter } from \"restql-ts/adapters\";\n\nconst adapter = createFastifyAdapter({\n  dialect: \"postgres\",\n  validation: defaultValidationOptions,\n});\n\nfastify.addHook(\"preHandler\", async (request, reply) =\u003e {\n  try {\n    const { sql, params } = await adapter.toSQL(request);\n    // Execute query...\n  } catch (error) {\n    if (error.name === \"ValidationError\") {\n      throw new Error(error.message);\n    }\n    throw error;\n  }\n});\n```\n\n### Complex Queries\n\n```typescript\n// Complex query with joins and nested conditions\nconst query = {\n  select: [\"users.id\", \"orders.total\"],\n  joins: [\n    {\n      type: \"LEFT\",\n      table: \"orders\",\n      alias: \"o\",\n      on: [{ field: \"users.id\", operator: \"=\", value: \"o.user_id\" }],\n    },\n  ],\n  where: {\n    operator: \"AND\",\n    conditions: [\n      {\n        operator: \"OR\",\n        conditions: [\n          { field: \"orders.total\", operator: \"\u003e\", value: 1000 },\n          { field: \"orders.status\", operator: \"=\", value: \"vip\" },\n        ],\n      },\n      { field: \"users.active\", operator: \"=\", value: true },\n    ],\n  },\n  orderBy: [{ field: \"orders.total\", direction: \"DESC\" }],\n  limit: 10,\n  offset: 0,\n};\n```\n\n## Security Features\n\nThe library includes comprehensive SQL injection prevention:\n\n```typescript\nimport { defaultValidationOptions } from \"restql-ts\";\n\nconst secureRestQL = createRestQL({\n  dialect: \"postgres\",\n  validation: {\n    ...defaultValidationOptions,\n    maxQueryDepth: 3, // Limit query complexity\n    maxConditionsPerGroup: 5, // Limit conditions per group\n    maxSelectFields: 20, // Limit number of fields\n    maxGroupByFields: 5, // Limit GROUP BY fields\n    maxValueLength: 1000, // Limit value length\n    preventSqlKeywords: true, // Prevent SQL keywords in values\n    allowedOperators: [\"=\", \"!=\", \"\u003e\", \"\u003c\"], // Restrict operators\n    allowedLogicalOperators: [\"AND\", \"OR\"], // Restrict logical operators\n    allowedFieldPattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, // Restrict field names\n  },\n});\n```\n\n### Security Validations\n\nField Name Protection:\nValidates against SQL injection patterns\nPrevents SQL keywords in field names\nEnforces safe character patterns\n\nValue Protection:\nValidates against SQL injection attempts\nPrevents dangerous characters\nLength limits\nSQL keyword prevention\n\nTable Name Protection:\nStrict table name pattern validation\nPrevents SQL keywords in table names\nLength limits on table names\n\nQuery Structure Protection:\nDepth limits for nested queries\nCondition count limits\nField count limits\n\n### Validation Options\n\n```typescript\ninterface ValidationOptions {\n  maxQueryDepth?: number; // Maximum depth of nested conditions\n  maxConditionsPerGroup?: number; // Maximum conditions in a WHERE group\n  maxSelectFields?: number; // Maximum fields in SELECT\n  maxGroupByFields?: number; // Maximum fields in GROUP BY\n  allowedOperators?: Operator[]; // Allowed comparison operators\n  allowedLogicalOperators?: LogicalOperator[]; // Allowed logical operators\n  allowedFieldPattern?: RegExp; // Pattern for valid field names\n  maxValueLength?: number; // Maximum length for values\n  preventSqlKeywords?: boolean; // Prevent SQL keywords in values\n}\n```\n\n## Query Structure\n\n### Select Query\n\n```typescript\ninterface QueryOptions {\n  select?: string[];\n  where?: WhereClause[];\n  joins?: JoinCondition[];\n  orderBy?: OrderByClause[];\n  groupBy?: string[];\n  having?: WhereClause[];\n  limit?: number;\n  offset?: number;\n}\n```\n\n### Where Conditions\n\n```typescript\ninterface WhereCondition {\n  field: string;\n  operator: Operator;\n  value: any;\n}\n\ninterface WhereGroup {\n  operator: \"AND\" | \"OR\";\n  conditions: (WhereCondition | WhereGroup)[];\n  not?: boolean;\n}\n```\n\n### Join Conditions\n\n```typescript\ninterface JoinCondition {\n  type: \"INNER\" | \"LEFT\" | \"RIGHT\" | \"FULL\";\n  table: string;\n  alias?: string;\n  on: WhereClause[];\n}\n```\n\n## Supported Operations\n\n- **SELECT**: Query data with complex conditions\n- **INSERT**: Single and bulk inserts\n- **UPDATE**: Single and bulk updates\n- **DELETE**: Single and bulk deletes\n\n## Error Handling\n\nThe library provides built-in error handling and validation:\n\n```typescript\ntry {\n  const { sql, params } = await adapter.toSQL(request);\n} catch (error) {\n  if (error instanceof QueryValidationError) {\n    // Handle validation errors\n  }\n  // Handle other errors\n}\n```\n\n## Documentation\n\nFor more detailed documentation, please visit:\n\n- [API Reference](docs/api.md) (WIP)\n- [Query Examples](docs/examples.md) (WIP)\n- [Adapters Guide](docs/adapters.md) (WIP)\n- [Contributing Guide](CONTRIBUTING.md)\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthe-forgebase%2Frestql-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthe-forgebase%2Frestql-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthe-forgebase%2Frestql-ts/lists"}