-
Notifications
You must be signed in to change notification settings - Fork 1
Add Drizzle ORM integration #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
de69cf4
docs: 📝 add Drizzle ORM integration guide
calvinbrewer 08933a1
test: ✅ add Drizzle ORM integration test and update Supabase tests
calvinbrewer 55dd990
fix: 🚨 replace isNaN with Number.isNaN and fix test any type
calvinbrewer 86e9fda
chore: 📦️ update package.json and lockfile
calvinbrewer 701f716
docs: 📝 update Drizzle ORM integration guide with encrypted type and …
calvinbrewer 9112af9
refactor: ♻️ extract Drizzle ORM integration into separate package
calvinbrewer 3ae95b4
Apply suggestion from @coderdan
calvinbrewer 21fd753
Apply suggestion from @coderdan
calvinbrewer 86e3311
feat: ✨ add batched operator support via protectOps.and()
calvinbrewer 6324cf3
Merge branch 'drizzle-orm' of https://github.com/cipherstash/protectj…
calvinbrewer a99517b
docs: 📝 add type safety documentation for encryptedType<T>
calvinbrewer 8150599
fix: 🐛 enable LazyOperatorPromise to work standalone and in and()
calvinbrewer 0092372
docs: 📝 document mixing regular Drizzle operators with Protect operat…
calvinbrewer 76325e1
chore: refactor operators
calvinbrewer 9b36560
ci: 👷 add CI database setup for drizzle package tests
calvinbrewer 12c6821
test: ✅ skip order by tests for CI database compatibility
calvinbrewer 60b64b9
docs: 📝 add comprehensive README for drizzle package integration
calvinbrewer a0e7c86
chore: ➖ remove drizzle-orm and postgres from protect package devDepe…
calvinbrewer d8ed4d4
chore: changeset
calvinbrewer 33d5789
feat(examples): update drizzle example with fintech use case
calvinbrewer 5ce557c
chore: pr feedback
calvinbrewer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@cipherstash/drizzle": minor | ||
| --- | ||
|
|
||
| Released initial Drizzle ORM interface. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@cipherstash/schema": minor | ||
| --- | ||
|
|
||
| Exported all types for packages looking for deeper integrations with Protect.js. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,319 @@ | ||
| # drizzle-eql | ||
| # Express REST API with Drizzle ORM and Protect.js | ||
|
|
||
| This is a example using the [drizzle-orm](https://drizzle-orm.com/). | ||
| This example demonstrates a FinTech REST API built with Express.js, Drizzle ORM, and Protect.js. It showcases how to encrypt sensitive financial data (account numbers, amounts, transaction descriptions) while maintaining the ability to search and query encrypted fields. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - PostgreSQL database | ||
| - CipherStash credentials and account | ||
| - **Node.js**: >= 22 | ||
| - **PostgreSQL**: Database with EQL v2 functions installed | ||
| - **CipherStash account**: For encryption credentials | ||
|
|
||
| ## Technologies | ||
|
|
||
| - [Express](https://expressjs.com/) - Web framework | ||
| - [Drizzle ORM](https://orm.drizzle.team/) - TypeScript ORM | ||
| - [Protect.js](https://github.com/cipherstash/protectjs) - End-to-end encryption | ||
| - [PostgreSQL](https://www.postgresql.org/) - Database | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. Create a PostgreSQL database and a user with read and write permissions. | ||
| 2. Create a `.env` file in the root directory of the project with the following content: | ||
| ### 1. Install Dependencies | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| ### 2. Set Up PostgreSQL with EQL v2 | ||
|
|
||
| Before running migrations, you need to install the EQL v2 types and functions in your PostgreSQL database: | ||
|
|
||
| ```bash | ||
| # Download the EQL install script | ||
| curl -sLo cipherstash-encrypt.sql https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql | ||
|
|
||
| # Install EQL types and functions | ||
| psql -d your_database -f cipherstash-encrypt.sql | ||
| ``` | ||
|
|
||
| This creates the `eql_v2_encrypted` composite type and search functions needed for searchable encryption. | ||
|
|
||
| ### 3. Environment Variables | ||
|
|
||
| Create an account on [CipherStash](https://dashboard.cipherstash.com/sign-up), create a workspace, then generate your client credentials. | ||
|
|
||
| Then, create a `.env` file in the root directory: | ||
|
|
||
| ```bash | ||
| # Database connection | ||
| DATABASE_URL="postgresql://[username]:[password]@[host]:5432/[database]" | ||
|
|
||
| # CipherStash credentials (generated in your CipherStash workspace) | ||
| CS_CLIENT_ID=[client-id] | ||
| CS_CLIENT_KEY=[client-key] | ||
| CS_WORKSPACE_ID=[workspace-id] | ||
| CS_WORKSPACE_CRN=[workspace-crn] | ||
| CS_CLIENT_ACCESS_KEY=[access-key] | ||
|
|
||
| # Optional: Server port (default: 3000) | ||
| PORT=3000 | ||
| ``` | ||
|
|
||
| 3. Run the following command to install the dependencies: | ||
| ### 4. Run Database Migrations | ||
|
|
||
| ```bash | ||
| npm install | ||
| pnpm db:migrate | ||
| ``` | ||
|
|
||
| 4. Run the following command to insert a new user with an encrypted email: | ||
| This creates the `transactions` table with encrypted columns. | ||
|
|
||
| ### 5. Start the Server | ||
|
|
||
| ```bash | ||
| npx tsx src/insert.ts --email [email protected] | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| 5. Run the following command to select all the encrypted emails from the database: | ||
| The server will start on `http://localhost:3000` (or the port specified in `PORT`). | ||
|
|
||
| ## API Endpoints | ||
|
|
||
| ### Health Check | ||
|
|
||
| **GET** `/health` | ||
|
|
||
| Returns server status. | ||
|
|
||
| ```bash | ||
| npx tsx src/select.ts | ||
| curl http://localhost:3000/health | ||
| ``` | ||
|
|
||
| Response: | ||
| ```json | ||
| { | ||
| "status": "ok", | ||
| "message": "Server is running" | ||
| } | ||
| ``` | ||
|
|
||
| ### List Transactions | ||
|
|
||
| **GET** `/transactions` | ||
|
|
||
| Retrieves all transactions with optional filters. | ||
|
|
||
| **Query Parameters:** | ||
| - `accountNumber` (string) - Search by account number (encrypted field, text search) | ||
| - `minAmount` (number) - Minimum transaction amount (encrypted field, range query) | ||
| - `maxAmount` (number) - Maximum transaction amount (encrypted field, range query) | ||
| - `status` (string) - Filter by status (non-encrypted field) | ||
|
|
||
| **Example:** | ||
| ```bash | ||
| # Get all transactions | ||
| curl http://localhost:3000/transactions | ||
|
|
||
| # Filter by account number | ||
| curl "http://localhost:3000/transactions?accountNumber=1234" | ||
|
|
||
| # Filter by amount range | ||
| curl "http://localhost:3000/transactions?minAmount=100&maxAmount=1000" | ||
|
|
||
| # Filter by status | ||
| curl "http://localhost:3000/transactions?status=completed" | ||
| ``` | ||
|
|
||
| > [!IMPORTANT] | ||
| > For production use, you should not use GET requests to filter data. | ||
| > Instead, you should use POST requests to filter data so sensitive data is not exposed in the URL. | ||
|
|
||
| **Response:** | ||
| ```json | ||
| { | ||
| "transactions": [ | ||
| { | ||
| "id": 1, | ||
| "accountNumber": "1234567890", | ||
| "amount": 500.00, | ||
| "description": "Payment for services", | ||
| "transactionType": "payment", | ||
| "status": "completed", | ||
| "createdAt": "2024-01-15T10:30:00Z", | ||
| "updatedAt": "2024-01-15T10:30:00Z" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ### Create Transaction | ||
|
|
||
| **POST** `/transactions` | ||
|
|
||
| Creates a new transaction with encrypted sensitive fields. | ||
|
|
||
| **Request Body:** | ||
| ```json | ||
| { | ||
| "accountNumber": "1234567890", | ||
| "amount": 500.00, | ||
| "description": "Payment for services", | ||
| "transactionType": "payment", | ||
| "status": "pending" | ||
| } | ||
| ``` | ||
|
|
||
| **Example:** | ||
| ```bash | ||
| curl -X POST http://localhost:3000/transactions \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "accountNumber": "1234567890", | ||
| "amount": 500.00, | ||
| "description": "Payment for services", | ||
| "transactionType": "payment", | ||
| "status": "pending" | ||
| }' | ||
| ``` | ||
|
|
||
| **Response:** | ||
| ```json | ||
| { | ||
| "transaction": { | ||
| "id": 1, | ||
| "accountNumber": "1234567890", | ||
| "amount": 500.00, | ||
| "description": "Payment for services", | ||
| "transactionType": "payment", | ||
| "status": "pending", | ||
| "createdAt": "2024-01-15T10:30:00Z", | ||
| "updatedAt": "2024-01-15T10:30:00Z" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Get Transaction by ID | ||
|
|
||
| **GET** `/transactions/:id` | ||
|
|
||
| Retrieves a single transaction by ID. | ||
|
|
||
| **Example:** | ||
| ```bash | ||
| curl http://localhost:3000/transactions/1 | ||
| ``` | ||
|
|
||
| **Response:** | ||
| ```json | ||
| { | ||
| "transaction": { | ||
| "id": 1, | ||
| "accountNumber": "1234567890", | ||
| "amount": 500.00, | ||
| "description": "Payment for services", | ||
| "transactionType": "payment", | ||
| "status": "completed", | ||
| "createdAt": "2024-01-15T10:30:00Z", | ||
| "updatedAt": "2024-01-15T10:30:00Z" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Update Transaction | ||
|
|
||
| **PUT** `/transactions/:id` | ||
|
|
||
| Updates a transaction. All fields are optional. | ||
|
|
||
| **Request Body:** | ||
| ```json | ||
| { | ||
| "accountNumber": "9876543210", | ||
| "amount": 750.00, | ||
| "description": "Updated description", | ||
| "transactionType": "refund", | ||
| "status": "completed" | ||
| } | ||
| ``` | ||
|
|
||
| **Example:** | ||
| ```bash | ||
| curl -X PUT http://localhost:3000/transactions/1 \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "status": "completed", | ||
| "amount": 750.00 | ||
| }' | ||
| ``` | ||
|
|
||
| **Response:** | ||
| ```json | ||
| { | ||
| "transaction": { | ||
| "id": 1, | ||
| "accountNumber": "1234567890", | ||
| "amount": 750.00, | ||
| "description": "Payment for services", | ||
| "transactionType": "payment", | ||
| "status": "completed", | ||
| "createdAt": "2024-01-15T10:30:00Z", | ||
| "updatedAt": "2024-01-15T11:00:00Z" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Delete Transaction | ||
|
|
||
| **DELETE** `/transactions/:id` | ||
|
|
||
| Deletes a transaction. | ||
|
|
||
| **Example:** | ||
| ```bash | ||
| curl -X DELETE http://localhost:3000/transactions/1 | ||
| ``` | ||
|
|
||
| **Response:** 204 No Content | ||
|
|
||
| ## Database Schema | ||
|
|
||
| The `transactions` table has the following structure: | ||
|
|
||
| - **Encrypted fields** (using `eql_v2_encrypted` type): | ||
| - `account_number` - Account number with equality and text search | ||
| - `amount` - Transaction amount with equality, range queries, and sorting | ||
| - `description` - Transaction description with text search | ||
|
|
||
| - **Non-encrypted fields**: | ||
| - `id` - Primary key (serial) | ||
| - `transaction_type` - Type of transaction (varchar) | ||
| - `status` - Transaction status (varchar, default: 'pending') | ||
| - `created_at` - Timestamp | ||
| - `updated_at` - Timestamp | ||
|
|
||
| ## How It Works | ||
|
|
||
| ### Encryption | ||
|
|
||
| - Sensitive fields (`accountNumber`, `amount`, `description`) are encrypted using Protect.js before being stored in the database | ||
| - The `@cipherstash/drizzle` package provides `encryptedType` helper to define encrypted columns in Drizzle schemas | ||
| - Data is automatically encrypted when inserting/updating and decrypted when reading | ||
|
|
||
| ### Searchable Encryption | ||
|
|
||
| - The API demonstrates searchable encryption capabilities: | ||
| - **Text search** on `accountNumber` and `description` using `ilike` operator | ||
| - **Range queries** on `amount` using `gte` and `lte` operators | ||
| - **Equality queries** on `accountNumber` and `amount` | ||
| - All encrypted field queries use Protect.js operators that automatically handle encryption | ||
|
|
||
| ### Type Safety | ||
|
|
||
| - TypeScript types are preserved throughout the encryption/decryption process | ||
| - The `encryptedType<T>` helper ensures decrypted values maintain their correct types | ||
|
|
||
| ## Notes | ||
|
|
||
| - **Native Module**: Protect.js uses `@cipherstash/protect-ffi`, a native Node-API module. Express doesn't bundle code, so no special configuration is needed. If deploying to serverless platforms, ensure the native module is properly externalized. | ||
| - **Error Handling**: All Protect.js operations return a Result type (`{ data }` or `{ failure }`). The API properly handles these results and returns appropriate HTTP status codes. | ||
| - **Bulk Operations**: The API uses `bulkEncryptModels` and `bulkDecryptModels` for efficient batch operations when querying multiple transactions. | ||
|
|
||
| ## License | ||
|
|
||
| This project is licensed under the MIT License. | ||
| This project is licensed under the MIT License. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| -- Drop old users table if it exists | ||
| DROP TABLE IF EXISTS "users"; | ||
|
|
||
| -- Create transactions table | ||
| CREATE TABLE IF NOT EXISTS "transactions" ( | ||
| "id" serial PRIMARY KEY NOT NULL, | ||
| "account_number" eql_v2_encrypted, | ||
| "amount" eql_v2_encrypted, | ||
| "description" eql_v2_encrypted, | ||
| "transaction_type" varchar(50) NOT NULL, | ||
| "status" varchar(20) NOT NULL DEFAULT 'pending', | ||
| "created_at" timestamp DEFAULT now() NOT NULL, | ||
| "updated_at" timestamp DEFAULT now() NOT NULL | ||
| ); | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine for demo but maybe add a comment that params should usually be done via POST if they are sensitive so that they don't appear in logs.