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 for tanstack example #2158

Open
wants to merge 6 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
7 changes: 5 additions & 2 deletions examples/tanstack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"license": "Apache-2.0",
"type": "module",
"scripts": {
"backend:up": "PROJECT_NAME=tanstack-example pnpm -C ../../ run example-backend:up && pnpm db:migrate",
"backend:down": "PROJECT_NAME=tanstack-example pnpm -C ../../ run example-backend:down",
"backend:up": "PROJECT_NAME=tanstack-example pnpm -C ../../ run example-backend:up && pnpm db:migrate",
"build": "vite build",
"db:migrate": "dotenv -e ../../.env.dev -- pnpm exec pg-migrations apply --directory ./db/migrations",
"dev": "concurrently \"vite\" \"node src/server/app.js\"",
Expand All @@ -25,10 +25,13 @@
"pg": "^8.12.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"serverless-http": "^3.2.0",
"sst": "3.3.71",
"uuid": "^10.0.0"
},
"devDependencies": {
"@databases/pg-migrations": "^5.0.3",
"@types/aws-lambda": "8.10.146",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
Expand All @@ -39,4 +42,4 @@
"typescript": "^5.5.3",
"vite": "^5.3.4"
}
}
}
17 changes: 14 additions & 3 deletions examples/tanstack/src/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ import "./Example.css"

type Item = { id: string }

const baseUrl = import.meta.env.ELECTRIC_URL ?? `http://localhost:3000`
const baseApiUrl = `http://localhost:3001`
const baseElectricUrl =
import.meta.env.VITE_ELECTRIC_URL ?? `http://localhost:3000`

const baseApiUrl =
import.meta.env.VITE_APP_BACKEND_URL ?? `http//localhost:3001`
const itemsUrl = new URL(`/items`, baseApiUrl)

const token = import.meta.env.VITE_ELECTRIC_TOKEN
const databaseId = import.meta.env.VITE_ELECTRIC_DATABASE_ID

const itemShape = () => ({
url: new URL(`/v1/shape`, baseUrl).href,
url: new URL(`/v1/shape`, baseElectricUrl).href,
params: {
table: `items`,
...(token ? { token } : {}),
...(databaseId ? { database_id: databaseId } : {}),
},
})

Expand All @@ -35,6 +43,9 @@ async function createItem(newId: string) {
const fetchPromise = fetch(itemsUrl, {
method: `POST`,
body: JSON.stringify({ id: newId }),
headers: {
"Content-Type": `application/json`,
},
})

return await Promise.all([findUpdatePromise, fetchPromise])
Expand Down
31 changes: 22 additions & 9 deletions examples/tanstack/src/server/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import http from "http"
import pg from "pg"
import serverless from "serverless-http"

const isProduction = process.env.NODE_ENV === `production`

const db = new pg.Pool({
connectionString:
Expand All @@ -20,11 +23,13 @@ const getRequestBody = async (req) => {
const JSON_HEADERS = {
"Content-Type": `application/json`,
}
const CORS_HEADERS = {
"Access-Control-Allow-Origin": `*`,
"Access-Control-Allow-Methods": `GET, POST, DELETE, OPTIONS`,
"Access-Control-Allow-Headers": `Content-Type`,
}
const CORS_HEADERS = isProduction
? {}
: {
"Access-Control-Allow-Origin": `*`,
"Access-Control-Allow-Methods": `GET, POST, DELETE, OPTIONS`,
"Access-Control-Allow-Headers": `Content-Type`,
}

const server = http.createServer(async (req, res) => {
console.log(req.method, req.url)
Expand Down Expand Up @@ -62,7 +67,15 @@ const server = http.createServer(async (req, res) => {
}
})

const PORT = process.env.PORT || 3001
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
let handler = server

if (!isProduction) {
const PORT = process.env.PORT || 3001
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
} else {
handler = serverless(server)
}

export { handler }
17 changes: 17 additions & 0 deletions examples/tanstack/src/server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@electric-examples/tanstack-backend",
"private": true,
"version": "0.0.1",
"author": "ElectricSQL",
"license": "Apache-2.0",
"type": "module",
"scripts": {
"dev": "node app.js"
},
"dependencies": {
"pg": "^8.12.0",
"serverless-http": "^3.2.0"
},
"devDependencies": {
}
}
19 changes: 19 additions & 0 deletions examples/tanstack/src/server/sst-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* 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 {
"Tanstack": {
"type": "sst.aws.StaticSite"
"url": string
}
"TanstackBackend": {
"name": string
"type": "sst.aws.Function"
"url": string
}
}
}
13 changes: 13 additions & 0 deletions examples/tanstack/src/sst-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_ELECTRIC_URL: string
readonly VITE_ELECTRIC_TOKEN: string
readonly VITE_ELECTRIC_DATABASE_ID: string
readonly VITE_APP_BACKEND_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
19 changes: 19 additions & 0 deletions examples/tanstack/sst-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* 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 {
"Tanstack": {
"type": "sst.aws.StaticSite"
"url": string
}
"TanstackBackend": {
"name": string
"type": "sst.aws.Function"
"url": string
}
}
}
135 changes: 135 additions & 0 deletions examples/tanstack/sst.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="./.sst/platform/config.d.ts" />

import { execSync } from "child_process"

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

export default $config({
app(input) {
return {
name: `tanstack`,
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 pg = new postgresql.Database(`tanstack`, {}, { 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 domainName = `tanstack${isProduction($app.stage) ? `` : `-stage-${$app.stage}`}.examples.electric-sql.com`

// We run the backend using AWS Lambda
const fun = new sst.aws.Function(`TanstackBackend`, {
handler: `src/server/app.handler`, // uses the `handler` export from the `src/server/app.js` file
environment: {
NODE_ENV: `production`,
DATABASE_URL: pgUri,
},
nodejs: {
install: [`pg`], // exclude `pg` module from the build, instead will install it into node_modules
},
url: {
cors: {
allowOrigins: [`https://` + domainName],
},
},
})

const staticSite = new sst.aws.StaticSite(`Tanstack`, {
environment: {
VITE_ELECTRIC_URL: process.env.ELECTRIC_API!,
VITE_ELECTRIC_TOKEN: electricInfo.token,
VITE_ELECTRIC_DATABASE_ID: electricInfo.id,
VITE_APP_BACKEND_URL: fun.url,
},
build: {
command: `npm run build`,
output: `dist`,
},
domain: {
name: domainName,
dns: sst.cloudflare.dns(),
},
})

pgUri.apply(applyMigrations)

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
}
}
Loading
Loading