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

chore (examples): cloud deployment of remix example #2153

Open
wants to merge 4 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
17 changes: 6 additions & 11 deletions examples/remix/app/db.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import pgPkg from "pg"
const { Client } = pgPkg
const { Pool } = pgPkg

const db = new Client({
host: `localhost`,
port: 54321,
password: `password`,
user: `postgres`,
database: `electric`,
})
const connectionString =
process.env.DATABASE_URL ??
`postgresql://postgres:password@localhost:54321/electric`
const pool = new Pool({ connectionString })

db.connect()

export { db }
export { pool }
6 changes: 3 additions & 3 deletions examples/remix/app/routes/api.items.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import nodePkg from "@remix-run/node"
const { json } = nodePkg
import type { ActionFunctionArgs } from "@remix-run/node"
import { db } from "../db"
import { pool } from "../db"

export async function action({ request }: ActionFunctionArgs) {
if (request.method === `POST`) {
const body = await request.json()
const result = await db.query(
const result = await pool.query(
`INSERT INTO items (id)
VALUES ($1) RETURNING id;`,
[body.uuid]
Expand All @@ -15,7 +15,7 @@ export async function action({ request }: ActionFunctionArgs) {
}

if (request.method === `DELETE`) {
await db.query(`DELETE FROM items;`)
await pool.query(`DELETE FROM items;`)

return `ok`
}
Expand Down
11 changes: 10 additions & 1 deletion examples/remix/app/routes/shape-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import type { LoaderFunctionArgs } from "@remix-run/node"

export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url)
const originUrl = new URL(`http://localhost:3000/v1/shape`)
const baseUrl = process.env.ELECTRIC_URL ?? `http://localhost:3000`
const originUrl = new URL(`/v1/shape`, baseUrl)
url.searchParams.forEach((value, key) => {
originUrl.searchParams.set(key, value)
})

if (process.env.DATABASE_ID) {
originUrl.searchParams.set(`database_id`, process.env.DATABASE_ID)
}

if (process.env.ELECTRIC_TOKEN) {
originUrl.searchParams.set(`token`, process.env.ELECTRIC_TOKEN)
}

// When proxying long-polling requests, content-encoding & content-length are added
// erroneously (saying the body is gzipped when it's not) so we'll just remove
// them to avoid content decoding errors in the browser.
Expand Down
14 changes: 9 additions & 5 deletions examples/remix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
"license": "Apache-2.0",
"type": "module",
"scripts": {
"backend:up": "PROJECT_NAME=remix-example pnpm -C ../../ run example-backend:up && pnpm db:migrate",
"backend:down": "PROJECT_NAME=remix-example pnpm -C ../../ run example-backend:down",
"backend:up": "PROJECT_NAME=remix-example pnpm -C ../../ run example-backend:up && pnpm db:migrate",
"build": "remix vite:build",
"db:migrate": "dotenv -e ../../.env.dev -- pnpm exec pg-migrations apply --directory ./db/migrations",
"dev": "vite",
"build": "vite build",
"stylecheck": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"dev": "remix vite:dev",
"format": "eslint . --fix",
"preview": "vite preview",
"start": "remix-serve ./build/server/index.js",
"stylecheck": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"typecheck": "tsc --noEmit"
},
"dependencies": {
Expand All @@ -26,13 +28,15 @@
"pg": "^8.12.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sst": "3.3.64",
"uuid": "^10.0.0"
},
"devDependencies": {
"@databases/pg-migrations": "^5.0.3",
"@types/aws-lambda": "8.10.146",
"@types/pg": "^8.11.6",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/pg": "^8.11.6",
"@types/uuid": "*",
"@vitejs/plugin-react": "^4.3.1",
"dotenv": "^16.4.5",
Expand Down
18 changes: 18 additions & 0 deletions examples/remix/sst-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
import "sst"
export {}
declare module "sst" {
export interface Resource {
"RemixExample": {
"name": string
"type": "sst.aws.Bucket"
}
"remix": {
"type": "sst.aws.Remix"
"url": string
}
}
}
117 changes: 117 additions & 0 deletions examples/remix/sst.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="./.sst/platform/config.d.ts" />

import { execSync } from "child_process"

const isProduction = (stage: string) => stage.toLowerCase() === `production`

export default $config({
app(input) {
return {
name: `remix`,
removal: input?.stage === `production` ? `retain` : `remove`,
protect: [`production`].includes(input?.stage),
home: `aws`,
providers: {
cloudflare: `5.42.0`,
aws: { version: `6.57.0`, region: `eu-west-1` },
postgresql: `3.14.0`,
},
}
},
async run() {
if (!process.env.ELECTRIC_API || !process.env.ELECTRIC_ADMIN_API)
throw new Error(
`Env variables ELECTRIC_API and ELECTRIC_ADMIN_API must be set`
)

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 provider = new postgresql.Provider(`neon`, {
host: process.env.EXAMPLES_DATABASE_HOST,
database: `neondb`,
username: `neondb_owner`,
password: process.env.EXAMPLES_DATABASE_PASSWORD,
})

const dbName = isProduction($app.stage)
? `remix-production`
: `remix-${$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 bucket = new sst.aws.Bucket(`RemixExample`)
const staticSite = new sst.aws.Remix(`remix`, {
link: [bucket],
environment: {
ELECTRIC_URL: process.env.ELECTRIC_API!,
ELECTRIC_TOKEN: electricInfo.token,
DATABASE_ID: electricInfo.id,
DATABASE_URL: pgUri,
},
domain: {
name: `remix${isProduction($app.stage) ? `` : `-stage-${$app.stage}`}.examples.electric-sql.com`,
dns: sst.cloudflare.dns(),
},
})

pgUri.apply((uri) => applyMigrations(uri))

return {
pgUri,
databaseId: electricInfo.id,
token: electricInfo.token,
url: staticSite.url,
}
},
})

function applyMigrations(uri: string) {
execSync(`pnpm exec pg-migrations apply --directory ./db/migrations`, {
env: {
...process.env,
DATABASE_URL: uri,
},
})
}

async function addDatabaseToElectric(
uri: string,
region: string
): Promise<{
id: string
token: string
}> {
const adminApi = process.env.ELECTRIC_ADMIN_API
const url = new URL(`/v1/databases`, adminApi)
const result = await fetch(url, {
method: `PUT`,
headers: { "Content-Type": `application/json` },
body: JSON.stringify({
database_url: uri,
region,
}),
})
if (!result.ok) {
throw new Error(
`Could not add database to Electric (${
result.status
}): ${await result.text()}`
)
}
return (await result.json()) as {
token: string
id: string
}
}
21 changes: 16 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading