Skip to content

Commit

Permalink
feat: add support for extra bindings (Hyperdrive) (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
atinux authored Aug 27, 2024
1 parent d397864 commit 6e548e5
Show file tree
Hide file tree
Showing 15 changed files with 497 additions and 342 deletions.
140 changes: 140 additions & 0 deletions docs/content/1.docs/3.recipes/5.postgres.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
title: PostgreSQL Database
navigation.title: PostgreSQL
description: Learn how to use PostgreSQL in your Nuxt application deployed on Cloudflare Workers / Pages and how to speed up your queries using Hyperdrive.
---

## Pre-requisites

Cloudflare does not host PostgreSQL databases, you need to setup your own PostgreSQL database.

::note{to="https://www.postgresql.org/support/professional_hosting/" target="_blank" icon="i-logos-postgresql"}
See a list of professional PostgreSQL hosting providers.
::

If you prefer to use Cloudflare services, you can use Cloudflare D1 which is built on SQLite, see our [Database](/docs/features/database) section.

## Setup

1. Make sure to use the `@nuxthub/core` module, see the [installation section](/docs/getting-started/installation#add-to-a-nuxt-project) for instructions.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxthub/core',],
});
```

::note
The module ensures that you can connect to your PostgreSQL database using [Cloudflare TCP Sockets](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/.)
::

2. Install the [`postgres`](https://www.npmjs.com/package/postgres) NPM package in your project.

```bash
npx nypm add postgres
```

::tip{icon="i-ph-rocket-launch"}
That's it, you can now use the `postgres` package to connect to your PostgreSQL database.
::

::warning
Please note that [`pg`](https://www.npmjs.com/package/pg) is not compatible at the moment.
::

## Usage

We can add our PostgreSQL database connection in our `.env` file.

```bash [.env]
NUXT_POSTGRES_URL=postgresql://user:password@localhost:5432/database
```

Then, we can create a `usePostgres()` server util to connect to our database in our API route.

```ts [server/utils/postgres.ts]
import postgres from 'postgres'

export function usePostgres () {
if (!process.env.NUXT_POSTGRES_URL) {
throw createError('Missing `NUXT_POSTGRES_URL` environment variable')
}

return postgres(process.env.NUXT_POSTGRES_URL as string, {
ssl: 'require'
})
}
```

We can now use the `usePostgres()` function to connect to our database in our API route.

```ts [server/api/db.ts]
export default eventHandler(async (event) => {
const sql = usePostgres()

const products = await sql`SELECT * FROM products`

// Ensure the database connection is closed after the request is processed
event.waitUntil(sql.end())
return products
})
```

::tip
You may notice that we don't import `usePostgres()`. This is because Nuxt auto-imports the exported variables & functions from `server/utils/*.ts` when used.
::

## Hyperdrive

[Hyperdrive](https://developers.cloudflare.com/hyperdrive/) is a Cloudflare service that accelerates queries you make to existing databases, making it faster to access your data from across the globe. By maintaining a connection pool to your database within Cloudflare’s network, Hyperdrive reduces seven round-trips to your database before you can even send a query: the TCP handshake (1x), TLS negotiation (3x), and database authentication (3x).

::important{to="https://developers.cloudflare.com/hyperdrive/platform/pricing/" target="_blank"}
Hyperdrive is only available on the Workers Paid plan ($5/month), **learn more**.
::

To use Hyperdrive in your Nuxt application:
1. [Create a Hyperdrive configuration](https://dash.cloudflare.com/?to=/:account/workers/hyperdrive/create)
2. Add your Hyperdrive ID in your `nuxt.config.ts` file

```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxthub/core'],
hub: {
bindings: {
hyperdrive: {
// <BINDING_NAME>: <HYPERDRIVE_ID>
POSTGRES: 'your-hyperdrive-id'
}
}
}
})
```

3. Update our `usePostgres()` function to use the `POSTGRES` binding when available.

```ts [server/utils/postgres.ts]
import type { Hyperdrive } from '@cloudflare/workers-types'
import postgres from 'postgres'

export function usePostgres() {
const hyperdrive = process.env.POSTGRES as Hyperdrive | undefined
const dbUrl = hyperdrive?.connectionString || process.env.NUXT_POSTGRES_URL
if (!dbUrl) {
throw createError('Missing `POSTGRES` hyperdrive binding or `NUXT_POSTGRES_URL` env variable')
}

return postgres(dbUrl, {
ssl: !hyperdrive ? 'require' : undefined
})
}
```

::warning
Hyperdrive is currently not available in development mode at the moment. We are working on a solution to make it work in development mode and remote storage with an upcoming `hubHyperdrive()`.
::

4. [Deploy your application](/docs/getting-started/deploy) with the NuxtHub CLI or Admin to make sure the Hyperdrive bindings are set.

::tip{icon="i-ph-rocket-launch"}
You can now access your PostgreSQL database from anywhere in the world at maximum speed.
::
27 changes: 27 additions & 0 deletions docs/content/4.changelog/hyperdrive-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: Hyperdrive bindings
description: "It is now possible to use PostgreSQL & Cloudflare Hyperdrive in your Nuxt application."
date: 2024-08-28
image: '/images/changelog/hyperdrive-bindings.png'
category: Admin
authors:
- name: Sebastien Chopin
avatar:
src: https://avatars.githubusercontent.com/u/904724?v=4
to: https://x.com/atinux
username: atinux
---

If you are not comfortable with Cloudflare D1 database (SQLite), you can now use your own PostgreSQL database.

We wrote a recipe on [How to use a PostgreSQL database](/docs/recipes/postgres) with NuxtHub.

This recipe explains how to connect to your PostgreSQL database and how to leverage Hyperdrive bindings in your Nuxt application to accelerate your database responses.

::callout
Right now, hyperdrive bindings are not available in development mode, we are working with the Cloudflare team to make them available in development mode as well with remote storage.
::

::tip
This feature is available on both [free and pro plans](/pricing) and in [`@nuxthub/core >= v0.7.6`](https://github.com/nuxt-hub/core/releases/tag/v0.7.6).
::
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@iconify-json/heroicons": "^1.1.24",
"@iconify-json/logos": "^1.1.44",
"@iconify-json/ph": "^1.1.14",
"@iconify-json/simple-icons": "^1.1.114",
"@nuxt/content": "^2.13.2",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 0 additions & 5 deletions docs/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,5 @@ export const asideLinks = [
label: 'Chat on Discord',
to: 'https://discord.gg/e25qqXb2mF',
target: '_blank'
}, {
icon: 'i-simple-icons-nuxtdotjs',
label: 'Nuxt Docs',
to: 'https://nuxt.com',
target: '_blank'
}
]
2 changes: 2 additions & 0 deletions playground/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Used for /api/hyperdrive
NUXT_POSTGRES_URL=
12 changes: 7 additions & 5 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ export default defineNuxtConfig({
database: true,
kv: true,
blob: true,
cache: true
cache: true,
bindings: {
// Used for /api/hyperdrive
hyperdrive: {
POSTGRES: '08f7bc805d1d409aac17e72af502abd0'
}
}
// projectUrl: ({ branch }) => branch === 'main' ? 'https://playground.nuxt.dev' : `https://${encodeHost(branch).replace(/\//g, '-')}.playground-to39.pages.dev`
},

ui: {
icons: ['heroicons', 'simple-icons']
},

basicAuth: {
enabled: process.env.NODE_ENV === 'production',
allowedRoutes: ['/api/_hub/'],
Expand Down
3 changes: 2 additions & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"@nuxt/ui": "^2.18.4",
"@nuxthub/core": "latest",
"drizzle-orm": "^0.33.0",
"nuxt": "^3.13.0"
"nuxt": "^3.13.0",
"postgres": "^3.4.4"
},
"devDependencies": {
"@nuxt/devtools": "latest"
Expand Down
37 changes: 37 additions & 0 deletions playground/server/api/hyperdrive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Hyperdrive } from '@cloudflare/workers-types'
import postgres from 'postgres'

export default eventHandler(async (event) => {
const hyperdrive = process.env.POSTGRES as Hyperdrive | undefined
const dbURL = hyperdrive?.connectionString || process.env.NUXT_POSTGRES_URL

if (!dbURL) {
throw createError({
statusCode: 500,
message: 'No process.env.NUXT_POSTGRES_URL or provess.env.HYPERDRIVE found'
})
}
const sql = postgres(dbURL, {
ssl: import.meta.dev ? 'require' : false
})

// Create products table
// await db.query('CREATE TABLE IF NOT EXISTS products (id SERIAL PRIMARY KEY, name VARCHAR(255), price DECIMAL(10, 2))')
// // Insert 10 products
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 1', 10.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 2', 20.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 3', 30.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 4', 40.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 5', 50.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 6', 60.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 7', 70.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 8', 80.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 9', 90.00])
// await db.query('INSERT INTO products (name, price) VALUES ($1, $2)', ['Product 10', 100.00])

console.log('query products')
const products = await sql`SELECT * FROM products`

event.waitUntil(sql.end())
return products
})
Loading

0 comments on commit 6e548e5

Please sign in to comment.