Skip to content
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

NextJs Example: fixes for serverless deployment #2103

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions examples/nextjs/app/api/items/route.ts
Original file line number Diff line number Diff line change
@@ -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`)
}
10 changes: 0 additions & 10 deletions examples/nextjs/app/db.ts

This file was deleted.

8 changes: 2 additions & 6 deletions examples/nextjs/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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` },
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions examples/nextjs/app/shape-proxy/route.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
84 changes: 44 additions & 40 deletions examples/nextjs/sst.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<neon.GetProjectResult>,
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,
}),
})

Expand Down
Loading