{"id":15365929,"url":"https://github.com/andywer/squid","last_synced_at":"2025-04-07T07:17:52.438Z","repository":{"id":42223112,"uuid":"162680648","full_name":"andywer/squid","owner":"andywer","description":"🦑 Provides SQL tagged template strings and schema definition functions.","archived":false,"fork":false,"pushed_at":"2023-02-28T05:57:42.000Z","size":352,"stargazers_count":150,"open_issues_count":15,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-31T06:07:35.960Z","etag":null,"topics":["database","postgres","postgresql","query-builder","schema","sql","sql-query","table-schema","typescript"],"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/andywer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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}},"created_at":"2018-12-21T07:22:23.000Z","updated_at":"2025-01-26T13:52:45.000Z","dependencies_parsed_at":"2024-06-18T18:43:20.431Z","dependency_job_id":null,"html_url":"https://github.com/andywer/squid","commit_stats":{"total_commits":79,"total_committers":7,"mean_commits":"11.285714285714286","dds":0.4177215189873418,"last_synced_commit":"6aaaaacc7bebcee3fc8e466a58ac771ddcdda030"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andywer%2Fsquid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andywer%2Fsquid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andywer%2Fsquid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andywer%2Fsquid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andywer","download_url":"https://codeload.github.com/andywer/squid/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247608160,"owners_count":20965953,"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":["database","postgres","postgresql","query-builder","schema","sql","sql-query","table-schema","typescript"],"created_at":"2024-10-01T13:16:43.684Z","updated_at":"2025-04-07T07:17:52.389Z","avatar_url":"https://github.com/andywer.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003esquid\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eSQL tagged template strings and schema definitions for JavaScript \u0026 TypeScript.\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://travis-ci.org/andywer/squid\"\u003e\u003cimg alt=\"Build status\" src=\"https://travis-ci.org/andywer/squid.svg?branch=master\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/squid\"\u003e\u003cimg alt=\"npm version\" src=\"https://img.shields.io/npm/v/squid.svg\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cbr /\u003e\n\nThe simple and safe way of writing SQL queries in node.js. Use [`postguard`](https://github.com/andywer/postguard) to validate SQL queries in your code against your table schemas at build time 🚀\n\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;👌\u0026nbsp;\u0026nbsp;Static typing made simple\u003cbr /\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;🛡\u0026nbsp;\u0026nbsp;SQL injection prevention\u003cbr /\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;🔦\u0026nbsp;\u0026nbsp;Static query validation using postguard\u003cbr /\u003e\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;⚡️\u0026nbsp;\u0026nbsp;Almost no performance overhead\u003cbr /\u003e\n\nParameters are SQL-injection-proofed by default. You can explicitly opt-out, by wrapping the parameter value in `sql.raw()`.\n\nSupports only Postgres right now, but it is easy to add support for MySQL, SQLite, ... as well. Create an issue or pull request if you need support for another database.\n\n## Why?\n\n#### Why not use a query builder?\n\nQuery builders like [Prisma](https://www.prisma.io/) or [Knex.js](https://knexjs.org/) seem like a good choice, but they all have one issue in common: They provide an abstraction that maps 1:1 to SQL, making you create SQL queries without writing SQL, but using their proprietary API.\n\nYou don't just require developers to learn both, SQL and the query builder's API, but the additional abstraction layer is also an additional source of error.\n\n#### Why not use an ORM?\n\nORMs like [Sequelize](http://docs.sequelizejs.com/) or [TypeORM](http://typeorm.io/) can get you started quickly, but will regularly lead to slow queries and can turn into a hassle in the long run. Read more about it [here](https://medium.com/ameykpatil/why-orm-shouldnt-be-your-best-bet-fffb66314b1b) and [here](https://blog.logrocket.com/why-you-should-avoid-orms-with-examples-in-node-js-e0baab73fa5), for instance.\n\n## Installation\n\n```sh\nnpm install squid\n```\n\n## Usage\n\n### JavaScript\n\n```js\nimport { defineTable, sql, spreadInsert } from \"squid/pg\"\nimport database from \"./database\"\n\n// Feel free to put the table schema in a different file\ndefineTable(\"users\", {\n  id: Schema.Number,\n  name: Schema.String\n})\n\nexport async function queryUserById(id) {\n  const { rows } = await database.query(sql`\n    SELECT * FROM users WHERE id = ${id}\n  `)\n  return rows.length \u003e 0 ? rows[0] : null\n}\n```\n\n### TypeScript\n\n```ts\n// schema.ts\nimport { defineTable, Schema, NewTableRow, TableRow } from \"squid\"\n\nexport type NewUserRecord = NewTableRow\u003ctypeof usersTable\u003e\nexport type UserRecord = TableRow\u003ctypeof usersTable\u003e\n\nconst usersTable = defineTable(\"users\", {\n  id: Schema.Number,\n  name: Schema.String\n})\n```\n\n```ts\n// users.ts\nimport { sql, spreadInsert } from \"squid/pg\"\nimport database from \"./database\"\nimport { NewUserRecord, UserRecord } from \"./schema\"\n\nexport async function createUser(record: NewUserRecord): Promise\u003cUserRecord\u003e {\n  const { rows } = await database.query\u003cUserRecord\u003e(sql`\n    INSERT INTO users ${spreadInsert(record)} RETURNING *\n  `)\n  return rows[0]\n}\n\nexport async function queryUserById(id: string): Promise\u003cUserRecord | null\u003e {\n  const { rows } = await database.query\u003cUserRecord\u003e(sql`\n    SELECT * FROM users WHERE id = ${id}\n  `)\n  return rows[0] || null\n}\n```\n\nWe extend the `pg` driver's `query()` method types transparently, so you can pass a generic type parameter specifying the type of the result rows as you can see in the sample above.\n\nThe `query()` type parameter defaults to `any`, so you don't have to specify it. If it's set, the type of the `rows` result property will be inferred accordingly.\n\n## Query values\n\nAll expressions in the SQL template strings will be escaped properly automatically, so you don't need to worry about SQL injection attacks too much.\n\nIf you need to pass a value dynamically that should not be escaped, you can use `sql.raw`:\n\n```js\nasync function updateTimestamp(userID, timestamp = null) {\n  await database.query(sql`\n    UPDATE users\n    SET timestamp = ${timestamp || sql.raw(\"NOW()\")}\n    WHERE id = ${userID}\n  `)\n}\n```\n\n## Tag function\n\nThe `sql` template tag creates query objects compatible with [`pg`](https://node-postgres.com), the super popular Postgres driver for node.\n\n```js\nimport { sql, spreadInsert } from \"squid/pg\"\n\nsql`INSERT INTO users ${spreadInsert({ name: \"Andy\", age: 29 })}`\n// =\u003e { text: \"INSERT INTO users (\"name\", \"age\") VALUES ($1, $2)\",\n//      values: [ \"Andy\", 29 ] }\n\nsql`SELECT * FROM users WHERE age \u003c ${maxAge}`\n// =\u003e { text: \"SELECT * FROM users WHERE age \u003c $1\",\n//      values: [ maxAge ] }\n```\n\n## Import\n\nAll schema-related exports are database-driver-agnostic, so they can be imported from the main entrypoint `squid`:\n\n```ts\nimport { defineTable, Schema, NewTableRow, TableRow } from \"squid\"\n```\n\nNon-schema-related exports are exported by the database-specific submodule `squid/pg`:\n\n```ts\nimport { sql, spreadInsert } from \"squid/pg\"\n```\n\nFor convenience `squid/pg` also exposes all the database-agnostic schema exports, so you can have one import declaration for everything:\n\n```ts\nimport { defineTable, sql, spreadInsert, Schema, NewTableRow, TableRow } from \"squid\"\n```\n\n## SQL injections\n\nAll values passed into the tagged template string are automatically escaped to prevent SQL injections. You have to explicitly opt-out of this behavior by using `sql.raw()` in case you want to dynamically modify the query.\n\nThe only attack vector left is forgetting to use the `sql` template tag. To rule out this potential source of error, there is also a way to explicitly escape a value passed to the template string: `sql.safe()`.\n\nAn untagged template string using a value wrapped in `sql.safe()` will then result in an invalid query.\n\n```js\n// This is fine\nawait database.query(sql`SELECT * FROM users LIMIT ${limit}`)\n\n// Results in the same query as the previous example\nawait database.query(sql`SELECT * FROM users LIMIT ${sql.safe(limit)}`)\n\n// Forgot the tag - SQL injection possible!\nawait database.query(`SELECT * FROM users LIMIT ${limit}`)\n\n// Forgot the tag - This line will now throw, no SQLi possible\nawait database.query(`SELECT * FROM users LIMIT ${sql.safe(limit)}`)\n```\n\n## API\n\n### sql\\`...\\`\n\nTurns a template string into a postgres query object, escapes values automatically unless they are wrapped in `sql.raw()`.\n\nExample:\n\n```js\nconst limit = 50\nawait database.query(sql`SELECT * FROM users LIMIT ${50}`)\n\n// same as:\nawait database.query({ text: \"SELECT * FROM users LIMIT $1\", values: [limit])\n```\n\n### sql.raw(expression)\n\nWrap your SQL template string values in this call to prevent escaping.\nBe careful, though. This is essentially an SQL injection prevention opt-out.\n\nExample:\n\n```js\nawait database.query(sql`\n  UPDATE users SET last_login = ${loggingIn ? \"NOW()\" : \"NULL\"} WHERE id = ${userID}\n`)\n```\n\n### sql.safe(value)\n\nWraps a value in an object that just returns the (escaped) value.\n\nUse it if you want to make sure that a query lacking the `sql` template tag cannot be executed. Otherwise a missing template string tag might lead to SQL injections.\n\n### spreadAnd({ [columnName: string]: any })\n\nCheck for equivalence of multiple column's values at once. Handy to keep long WHERE expressions short and concise.\n\nExample:\n\n```js\nconst users = await database.query(sql`\n  SELECT * FROM users WHERE ${spreadAnd({ name: \"John\", birthday: \"1990-09-10\" })}\n`)\n\n// same as:\n// sql`SELECT * FROM users WHERE name = 'John' AND birthday = '1990-09-10'`\n```\n\n### spreadInsert({ [columnName: string]: any })\n\nSpread INSERT VALUES to keep the query sweet and short without losing explicity.\n\nExample:\n\n```js\nconst users = await database.query(sql`\n  INSERT INTO users ${spreadInsert({ name: \"John\", email: \"john@example.com\" })}\n`)\n\n// same as:\n// sql`INSERT INTO users (\"name\", \"email\") VALUES ('John', 'john@example.com')`\n```\n\n```js\nconst users = await database.query(sql`\n  INSERT INTO users ${spreadInsert(\n    { name: \"John\", email: \"john@example.com\" },\n    { name: \"Travis\", email: \"travis@example.com\" }\n  )}\n`)\n\n// same as:\n// sql`INSERT INTO users (\"name\", \"email\") VALUES ('John', 'john@example.com'), ('Travis', 'travis@example.com')`\n```\n\n### spreadUpdate({ [columnName: string]: any })\n\nSpread INSERT VALUES to keep the query sweet and short without losing explicity.\n\nExample:\n\n```js\nawait database.query(sql`\n  UPDATE users\n  SET ${spreadUpdate({ name: \"John\", email: \"john@example.com\" })}\n  WHERE id = 1\n`)\n\n// same as:\n// sql`UPDATE users SET \"name\" = 'John', \"email\" = 'john@example.com' WHERE id = 1`\n```\n\n### defineTable(tableName: string, schema: { [columnName: string]: Schema.\\* })\n\nDefine a table's schema, so the queries can be validated at build time with `postguard`. When using TypeScript you can use `TableRow\u003ctypeof table\u003e` and `NewTableRow\u003ctypeof table\u003e` to derive TypeScript interfaces of your table records.\n\nSee [dist/schema.d.ts](./dist/schema.d.ts) for details.\n\n### Schema\n\nExample:\n\n```js\ndefineTable(\"users\", {\n  id: Schema.Number,\n  email: Schema.String,\n  email_confirmed: Schema.Boolean,\n  profile: Schema.JSON(\n    Schema.Object({\n      avatar_url: Schema.String,\n      weblink: Schema.nullable(Schema.String)\n    })\n  ),\n  created_at: Schema.default(Schema.Date),\n  updated_at: Schema.nullable(Schema.Date),\n  roles: Schema.Array(Schema.Enum([\"admin\", \"user\"]))\n})\n```\n\nSee [dist/schema.d.ts](./dist/schema.d.ts) for details.\n\n### TableRow\u003ctype\u003e / NewTableRow\u003ctype\u003e _(TypeScript only)_\n\nDerive table record interfaces from the table schema. The type returned by `TableRow` is the kind of object a `SELECT *` will return, while `NewTableRow` returns an object that defines the shape of an object to be used for an `INSERT` with `spreadInsert()`.\n\nThe difference between the two is that `NewTableRow` marks properties referring to columns defined as `Schema.default()` or `Schema.nullable()` as optional.\n\nExample:\n\n```ts\nconst usersTable = defineTable(\"users\", {\n  id: Schema.Number,\n  email: Schema.String\n})\n\ntype UserRecord = TableRow\u003ctypeof usersTable\u003e\ntype NewUserRecord = NewTableRow\u003ctypeof usersTable\u003e\n```\n\nSee [dist/schema.d.ts](./dist/schema.d.ts) for details.\n\n## Performance\n\nThe performance impact of using the template string is neglectible. Benchmarked it once and it did 1000 queries in ~10ms on my MacBook Pro.\n\n## Debugging\n\nSet the environment variable `DEBUG` to `squid:*` to enable debug logging for this package.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandywer%2Fsquid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandywer%2Fsquid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandywer%2Fsquid/lists"}