Skip to content

Commit

Permalink
refactor: use planetscale -> supabase (#1024)
Browse files Browse the repository at this point in the history
* move to supabase

* readme

* auth bump

* fix typecheck
  • Loading branch information
juliusmarminge authored May 11, 2024
1 parent 0a6f0d2 commit f7f7bd9
Show file tree
Hide file tree
Showing 14 changed files with 388 additions and 321 deletions.
8 changes: 3 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
# This file will be committed to version control, so make sure not to have any secrets in it.
# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.

# The database URL is used to connect to your PlanetScale database.
DB_HOST='aws.connect.psdb.cloud'
DB_NAME='YOUR_DB_NAME'
DB_USERNAME=''
DB_PASSWORD='pscale_pw_'
# The database URL is used to connect to your Supabase database.
POSTGRES_URL="postgres://postgres.[USERNAME]:[PASSWORD]@aws-0-eu-central-1.pooler.supabase.com:6543/postgres?workaround=supabase-pooler.vercel"


# You can generate the secret via 'openssl rand -base64 32' on Unix
# @see https://next-auth.js.org/configuration/options#secret
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ packages
├─ auth
| └─ Authentication using next-auth. **NOTE: Only for Next.js app, not Expo**
├─ db
| └─ Typesafe db calls using Drizzle & Planetscale
| └─ Typesafe db calls using Drizzle & Supabase
└─ ui
└─ Start of a UI package for the webapp using shadcn-ui
tooling
Expand All @@ -70,7 +70,7 @@ tooling
## Quick Start

> **Note**
> The [db](./packages/db) package is preconfigured to use PlanetScale and is **edge-bound** with the [database.js](https://github.com/planetscale/database-js) driver. If you're using something else, make the necessary modifications to the [schema](./packages/db/src/schema) as well as the [client](./packages/db/src/index.ts) and the [drizzle config](./packages/db/drizzle.config.ts). If you want to switch to non-edge database driver, remove `export const runtime = "edge";` [from all pages and api routes](https://github.com/t3-oss/create-t3-turbo/issues/634#issuecomment-1730240214).
> The [db](./packages/db) package is preconfigured to use Supabase and is **edge-bound** with the [Vercel Postgres](https://github.com/vercel/storage/tree/main/packages/postgres) driver. If you're using something else, make the necessary modifications to the [schema](./packages/db/src/schema) as well as the [client](./packages/db/src/index.ts) and the [drizzle config](./packages/db/drizzle.config.ts). If you want to switch to non-edge database driver, remove `export const runtime = "edge";` [from all pages and api routes](https://github.com/t3-oss/create-t3-turbo/issues/634#issuecomment-1730240214).
To get it running, follow the steps below:

Expand Down
2 changes: 1 addition & 1 deletion apps/auth-proxy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@auth/core": "0.30.0"
"@auth/core": "0.31.0"
},
"devDependencies": {
"@acme/eslint-config": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/src/app/post/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { api } from "~/utils/api";
export default function Post() {
const { id } = useGlobalSearchParams();
if (!id || typeof id !== "string") throw new Error("unreachable");
const { data } = api.post.byId.useQuery({ id: parseInt(id) });
const { data } = api.post.byId.useQuery({ id });

if (!data) return null;

Expand Down
5 changes: 1 addition & 4 deletions apps/nextjs/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ export const env = createEnv({
* This way you can ensure the app isn't built with invalid env vars.
*/
server: {
DB_HOST: z.string(),
DB_NAME: z.string(),
DB_PASSWORD: z.string(),
DB_USERNAME: z.string(),
POSTGRES_URL: z.string().url(),
},

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/router/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const postRouter = {
}),

byId: publicProcedure
.input(z.object({ id: z.number() }))
.input(z.object({ id: z.string() }))
.query(({ ctx, input }) => {
// return ctx.db
// .select()
Expand All @@ -34,7 +34,7 @@ export const postRouter = {
return ctx.db.insert(schema.post).values(input);
}),

delete: protectedProcedure.input(z.number()).mutation(({ ctx, input }) => {
delete: protectedProcedure.input(z.string()).mutation(({ ctx, input }) => {
return ctx.db.delete(schema.post).where(eq(schema.post.id, input));
}),
} satisfies TRPCRouterRecord;
4 changes: 2 additions & 2 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
},
"dependencies": {
"@acme/db": "workspace:*",
"@auth/drizzle-adapter": "^1.0.1",
"@auth/drizzle-adapter": "^1.1.0",
"@t3-oss/env-nextjs": "^0.10.1",
"next": "^14.2.3",
"next-auth": "5.0.0-beta.17",
"next-auth": "5.0.0-beta.18",
"react": "18.3.1",
"react-dom": "18.3.1",
"zod": "^3.23.6"
Expand Down
7 changes: 3 additions & 4 deletions packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
"clean": "rm -rf .turbo node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
"push": "pnpm with-env drizzle-kit push:mysql --config src/config.ts",
"push": "pnpm with-env drizzle-kit push --config src/config.ts",
"studio": "pnpm with-env drizzle-kit studio --config src/config.ts",
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@planetscale/database": "^1.18.0",
"@vercel/postgres": "^0.8.0",
"@t3-oss/env-core": "^0.10.1",
"drizzle-orm": "^0.30.10",
"zod": "^3.23.6"
Expand All @@ -32,9 +32,8 @@
"@acme/prettier-config": "workspace:*",
"@acme/tsconfig": "workspace:*",
"dotenv-cli": "^7.4.1",
"drizzle-kit": "^0.20.18",
"drizzle-kit": "^0.21.1",
"eslint": "^9.2.0",
"mysql2": "^3.9.7",
"prettier": "^3.2.5",
"typescript": "^5.4.5"
},
Expand Down
25 changes: 6 additions & 19 deletions packages/db/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import type { Config } from "drizzle-kit";
import { createEnv } from "@t3-oss/env-core";
import * as z from "zod";

const env = createEnv({
server: {
DB_HOST: z.string(),
DB_NAME: z.string(),
DB_USERNAME: z.string(),
DB_PASSWORD: z.string(),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
});
if (!process.env.POSTGRES_URL) {
throw new Error("Missing POSTGRES_URL");
}

// Push requires SSL so use URL instead of username/password
export const connectionStr = new URL(`mysql://${env.DB_HOST}/${env.DB_NAME}`);
connectionStr.username = env.DB_USERNAME;
connectionStr.password = env.DB_PASSWORD;
connectionStr.searchParams.set("ssl", '{"rejectUnauthorized":true}');
const nonPoolingUrl = process.env.POSTGRES_URL.replace(":6543", ":5432");

This comment has been minimized.

Copy link
@trevorpfiz

trevorpfiz May 13, 2024

Curious to know the reason behind this. I see that Supabase recommends 6543 with @vercel/postgres, https://supabase.com/docs/guides/database/connecting-to-postgres/serverless-drivers#manual-configuration, but the @vercel/postgres client with Neon is 5432, https://orm.drizzle.team/learn/tutorials/drizzle-with-vercel-edge-functions#vercel-postgres-client.


export default {
schema: "./src/schema",
driver: "mysql2",
dbCredentials: { uri: connectionStr.href },
dialect: "postgresql",
dbCredentials: { url: nonPoolingUrl },
tablesFilter: ["t3turbo_*"],
} satisfies Config;
8 changes: 3 additions & 5 deletions packages/db/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Client } from "@planetscale/database";
import { drizzle } from "drizzle-orm/planetscale-serverless";
import { sql } from "@vercel/postgres";
import { drizzle } from "drizzle-orm/vercel-postgres";

import { connectionStr } from "./config";
import * as auth from "./schema/auth";
import * as post from "./schema/post";

Expand All @@ -10,5 +9,4 @@ export { alias } from "drizzle-orm/mysql-core";

export const schema = { ...auth, ...post };

const psClient = new Client({ url: connectionStr.href });
export const db = drizzle(psClient, { schema });
export const db = drizzle(sql, { schema });
4 changes: 2 additions & 2 deletions packages/db/src/schema/_table.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { mysqlTableCreator } from "drizzle-orm/mysql-core";
import { pgTableCreator } from "drizzle-orm/pg-core";

/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM.
* Use the same database instance for multiple projects.
*
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/
export const mySqlTable = mysqlTableCreator((name) => `t3turbo_${name}`);
export const pgTable = pgTableCreator((name) => `t3turbo_${name}`);
59 changes: 30 additions & 29 deletions packages/db/src/schema/auth.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import { relations, sql } from "drizzle-orm";
import { relations } from "drizzle-orm";
import {
index,
int,
integer,
primaryKey,
text,
timestamp,
uuid,
varchar,
} from "drizzle-orm/mysql-core";
} from "drizzle-orm/pg-core";

import { mySqlTable } from "./_table";
import { pgTable } from "./_table";

export const users = mySqlTable("user", {
id: varchar("id", { length: 255 }).notNull().primaryKey(),
export const users = pgTable("user", {
id: uuid("id").notNull().primaryKey().defaultRandom(),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).notNull(),
emailVerified: timestamp("emailVerified", {
mode: "date",
fsp: 3,
}).default(sql`CURRENT_TIMESTAMP(3)`),
withTimezone: true,
}),
image: varchar("image", { length: 255 }),
});

export const usersRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
}));

export const accounts = mySqlTable(
export const accounts = pgTable(
"account",
{
userId: varchar("userId", { length: 255 }).notNull(),
userId: uuid("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type<"oauth" | "oidc" | "email">()
.$type<"email" | "oauth" | "oidc" | "webauthn">()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: text("access_token"),
expires_at: int("expires_at"),
expires_at: integer("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: text("id_token"),
Expand All @@ -46,38 +48,37 @@ export const accounts = mySqlTable(
compoundKey: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
userIdIdx: index("userId_idx").on(account.userId),
}),
);

export const accountsRelations = relations(accounts, ({ one }) => ({
user: one(users, { fields: [accounts.userId], references: [users.id] }),
}));

export const sessions = mySqlTable(
"session",
{
sessionToken: varchar("sessionToken", { length: 255 })
.notNull()
.primaryKey(),
userId: varchar("userId", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(session) => ({
userIdIdx: index("userId_idx").on(session.userId),
}),
);
export const sessions = pgTable("session", {
sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
userId: uuid("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", {
mode: "date",
withTimezone: true,
}).notNull(),
});

export const sessionsRelations = relations(sessions, ({ one }) => ({
user: one(users, { fields: [sessions.userId], references: [users.id] }),
}));

export const verificationTokens = mySqlTable(
export const verificationTokens = pgTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
expires: timestamp("expires", {
mode: "date",
withTimezone: true,
}).notNull(),
},
(vt) => ({
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
Expand Down
17 changes: 10 additions & 7 deletions packages/db/src/schema/post.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { sql } from "drizzle-orm";
import { serial, timestamp, varchar } from "drizzle-orm/mysql-core";
import { text, timestamp, uuid, varchar } from "drizzle-orm/pg-core";

import { mySqlTable } from "./_table";
import { pgTable } from "./_table";

export const post = mySqlTable("post", {
id: serial("id").primaryKey(),
export const post = pgTable("post", {
id: uuid("id").notNull().primaryKey().defaultRandom(),
title: varchar("name", { length: 256 }).notNull(),
content: varchar("content", { length: 256 }).notNull(),
content: text("content").notNull(),
createdAt: timestamp("created_at")
.default(sql`CURRENT_TIMESTAMP`)
.default(sql`now()`)
.notNull(),
updatedAt: timestamp("updatedAt").onUpdateNow(),
updatedAt: timestamp("updatedAt", {
mode: "date",
withTimezone: true,
}).$onUpdateFn(() => sql`now()`),
});
Loading

0 comments on commit f7f7bd9

Please sign in to comment.