diff --git a/examples/nextjs/app/api/items/route.ts b/examples/nextjs/app/api/items/route.ts index 4d2ed7dd99..b6480015c2 100644 --- a/examples/nextjs/app/api/items/route.ts +++ b/examples/nextjs/app/api/items/route.ts @@ -1,17 +1,23 @@ -import { db } from "../../db" import { NextResponse } from "next/server" +import { Pool } from "pg" + +const client = new Pool({ + connectionString: + process.env.DATABASE_URL ?? + `postgresql://postgres:password@localhost:54321/electric`, +}) export async function POST(request: Request) { const body = await request.json() - const result = await db.query( + const result = await client.query( `INSERT INTO items (id) - VALUES ($1) RETURNING id;`, + VALUES ($1) RETURNING id;`, [body.uuid] ) return NextResponse.json({ id: result.rows[0].id }) } export async function DELETE() { - await db.query(`DELETE FROM items;`) + await client.query(`DELETE FROM items;`) return NextResponse.json(`ok`) } diff --git a/examples/nextjs/app/db.ts b/examples/nextjs/app/db.ts deleted file mode 100644 index 4410f51fb3..0000000000 --- a/examples/nextjs/app/db.ts +++ /dev/null @@ -1,10 +0,0 @@ -import pgPkg from "pg" -const { Client } = pgPkg - -const db = new Client({ - connectionString: process.env.DATABASE_URL, -}) - -db.connect() - -export { db } diff --git a/examples/nextjs/app/page.tsx b/examples/nextjs/app/page.tsx index 50963659ba..fee2144af3 100644 --- a/examples/nextjs/app/page.tsx +++ b/examples/nextjs/app/page.tsx @@ -11,16 +11,12 @@ const itemShape = (): ShapeStreamOptions => { if (typeof window !== `undefined`) { return { url: new URL(`/shape-proxy`, window?.location.origin).href, - params: { - table: `items`, - }, + params: { table: `items` }, } } else { return { url: new URL(`https://not-sure-how-this-works.com/shape-proxy`).href, - params: { - table: `items`, - }, + params: { table: `items` }, } } } diff --git a/examples/nextjs/app/shape-proxy/route.ts b/examples/nextjs/app/shape-proxy/route.ts index aadfae4d3a..fc16c915da 100644 --- a/examples/nextjs/app/shape-proxy/route.ts +++ b/examples/nextjs/app/shape-proxy/route.ts @@ -1,9 +1,8 @@ export async function GET(request: Request) { const url = new URL(request.url) const originUrl = new URL( - process.env.ELECTRIC_URL - ? `${process.env.ELECTRIC_URL}/v1/shape` - : `http://localhost:3000/v1/shape` + `/v1/shape`, + process.env.ELECTRIC_URL ?? `http://localhost:3000` ) url.searchParams.forEach((value, key) => { diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index cd230b73bf..a320bb7cf8 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -8,11 +8,11 @@ "scripts": { "backend:up": "PROJECT_NAME=nextjs-example pnpm -C ../../ run example-backend:up && pnpm db:migrate", "backend:down": "PROJECT_NAME=nextjs-example pnpm -C ../../ run example-backend:down", + "build": "next build", "db:migrate": "dotenv -e ../../.env.dev -- pnpm exec pg-migrations apply --directory ./db/migrations", "dev": "next dev --turbo -p 5173", - "build": "next build", - "start": "next start", "format": "eslint . --fix", + "start": "next start", "stylecheck": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "typecheck": "tsc --noEmit" }, diff --git a/examples/nextjs/sst.config.ts b/examples/nextjs/sst.config.ts index a77237e859..22a3aa5c4c 100644 --- a/examples/nextjs/sst.config.ts +++ b/examples/nextjs/sst.config.ts @@ -3,48 +3,63 @@ import { execSync } from "child_process" +const isProduction = (stage) => stage.toLowerCase() === `production` + export default $config({ app(input) { return { name: `nextjs-example`, - removal: input?.stage === `production` ? `retain` : `remove`, + removal: isProduction(input?.stage) ? `retain` : `remove`, home: `aws`, providers: { cloudflare: `5.42.0`, aws: { version: `6.57.0`, + region: `eu-west-1`, }, - neon: `0.6.3`, + postgresql: `3.14.0`, }, } }, async run() { - const project = neon.getProjectOutput({ id: process.env.NEON_PROJECT_ID! }) - const base = { - projectId: project.id, - branchId: project.defaultBranchId, - } + if (!process.env.ELECTRIC_API || !process.env.ELECTRIC_ADMIN_API) + throw new Error( + `Env variables ELECTRIC_API and ELECTRIC_ADMIN_API must be set` + ) - const db = new neon.Database(`nextjs`, { - ...base, - name: - $app.stage === `Production` - ? `nextjs-production` - : `nextjs-${$app.stage}`, - ownerName: `neondb_owner`, - }) + if ( + !process.env.EXAMPLES_DATABASE_HOST || + !process.env.EXAMPLES_DATABASE_PASSWORD + ) { + throw new Error( + `Env variables EXAMPLES_DATABASE_HOST and EXAMPLES_DATABASE_PASSWORD must be set` + ) + } - const databaseUri = getNeonDbUri(project, db) try { - databaseUri.apply(applyMigrations) + const provider = new postgresql.Provider(`neon`, { + host: process.env.EXAMPLES_DATABASE_HOST, + database: `neondb`, + username: `neondb_owner`, + password: process.env.EXAMPLES_DATABASE_PASSWORD, + }) - const electricInfo = databaseUri.apply((uri) => - addDatabaseToElectric(uri) - ) + const dbName = isProduction($app.stage) + ? `nextjs-production` + : `nextjs-${$app.stage}` + const pg = new postgresql.Database(dbName, {}, { provider }) + + const pgUri = $interpolate`postgresql://${provider.username}:${provider.password}@${provider.host}/${pg.name}?sslmode=require` + const electricInfo = pgUri.apply((uri) => { + return addDatabaseToElectric(uri, `eu-west-1`) + }) + + const website = deployNextJsExample(electricInfo, pgUri) + + pgUri.apply((uri) => applyMigrations(uri)) - const website = deployNextJsExample(electricInfo, databaseUri) return { - databaseUri, + pgUri, database_id: electricInfo.id, electric_token: electricInfo.token, website: website.url, @@ -76,36 +91,25 @@ function deployNextJsExample( DATABASE_URL: uri, }, domain: { - name: `nextjs${$app.stage === `production` ? `` : `-stage-${$app.stage}`}.electric-sql.com`, + name: `nextjs${isProduction($app.stage) ? `` : `-stage-${$app.stage}`}.examples.electric-sql.com`, dns: sst.cloudflare.dns(), }, }) } -function getNeonDbUri( - project: $util.Output, - db: neon.Database -) { - const passwordOutput = neon.getBranchRolePasswordOutput({ - projectId: project.id, - branchId: project.defaultBranchId, - roleName: db.ownerName, - }) - - return $interpolate`postgresql://${passwordOutput.roleName}:${passwordOutput.password}@${project.databaseHost}/${db.name}?sslmode=require` -} - async function addDatabaseToElectric( - uri: string + uri: string, + region: `eu-west-1` | `us-east-1` ): Promise<{ id: string; token: string }> { - const adminApi = process.env.ELECTRIC_ADMIN_API + const adminApi = process.env.ELECTRIC_ADMIN_API! - const result = await fetch(`${adminApi}/v1/databases`, { + const electricUrl = new URL(`/v1/databases`, adminApi) + const result = await fetch(electricUrl, { method: `PUT`, headers: { "Content-Type": `application/json` }, body: JSON.stringify({ database_url: uri, - region: `us-east-1`, + region, }), })