Skip to content

Commit

Permalink
feat: add drizzle (#2)
Browse files Browse the repository at this point in the history
* feat: add drizzle

* docs: update readme
  • Loading branch information
notrab authored Aug 14, 2024
1 parent 32d2420 commit 600101e
Show file tree
Hide file tree
Showing 11 changed files with 1,189 additions and 47 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

.env
62 changes: 40 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,74 +10,91 @@ A Next.js application that demonstrates how to use the [Turso](https://turso.tec

## Setup

1. **Create a parent schema database with Turso:**
1. Create a new SQLite multi-db schema database with Turso:

[![Create Database](https://sqlite.new/button)](https://sqlite.new?dump=https%3A%2F%2Fraw.githubusercontent.com%2Fnotrab%2Fturso-platforms-starter%2Fmain%2Fdump.sql&type=schema)
```bash
turso db create <database-name> --schema
```

2. Create a new group token:

2. **Run `cp .env.example .env.local`, add your Turso username (GitHub or custom) and database name to it:**
Create a new token that can access all databases in your group:

```bash
TURSO_SCHEMA_DATABASE_NAME=
turso group tokens create <group-name>
```

3. **[Sign up to Clerk](https://clerk.com)**
> [!NOTE]
> If you didn't already have one, a new group will be created for you with the name `default`.
2. **Run `cp .env.example .env`**

Add your Clerk public key and secret key to the `.env.local` file:
Add your database name, group token, and Turso organization name to the `.env` file:

```bash
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
TURSO_SCHEMA_DATABASE_NAME=
TURSO_DATABASE_GROUP_AUTH_TOKEN=
TURSO_ORG_NAME=
```

4. **Create a new Clerk webhook**
> [!NOTE]
> The `TURSO_ORG_NAME` can be your personal username, or the name of any organization you have with other users.
Make sure to use the `user.created` event, and pass it your production or local URL (using ngrok). Append `/webhooks/clerk` to the URL.
3. Migrate the database

5. **Set the webhook secret in the `.env.local` file:**
Run the following command to create the tables in the database:

```bash
CLERK_WEBHOOK_SECRET=
npm run db:migrate
```

6. **Create a new group token with Turso**
> [!NOTE]
> If you make changes to `db/schema.ts`, make sure to run `npm run db:generate` to create the migrations, and `npm run db:migrate` to apply them.
4. **[Sign up to Clerk](https://clerk.com)**

You can use the Turso Dashboard, or [install the Turso CLI](https://docs.turso.tech/cli/installation) and run:
Add your Clerk public key and secret key to the `.env` file:

```bash
turso group tokens create <group-name>
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
```

Set the group token in the `.env.local` file:
5. **Create a new Clerk webhook**

Make sure to use the `user.created` event, and pass it your production or local URL (using ngrok). Append `/webhooks/clerk` to the URL.

5. **Set the webhook secret in the `.env` file:**

```bash
TURSO_DATABASE_GROUP_AUTH_TOKEN=
CLERK_WEBHOOK_SECRET=
```

7. **Create a Turso API Token using the Dashboard, or CLI:**
6. **Create a Turso API Token:**

```bash
turso auth api-tokens mint clerk
```

Set the API token in the `.env.local` file:
Set the API token in the `.env` file:

```bash
TURSO_USER_API_TOKEN=
```

8. **Install dependencies:**
7. **Install dependencies:**

```bash
npm install
```

9. **Run the development server:**
8. **Run the development server:**

```bash
npm run dev
```

10. **Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.**
9. **Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.**

## Deploy to production

Expand All @@ -88,6 +105,7 @@ When you're ready to deploy to production, make sure to configure Clerk for prod
## Tech Stack

- [Turso](https://turso.tech)
- [Drizzle ORM/Kit](https://orm.drizzle.team)
- [Next.js](https://nextjs.org)
- [Tailwind CSS](https://tailwindcss.com)
- [Clerk](https://clerk.com)
Expand Down
15 changes: 6 additions & 9 deletions app/(authenticated)/dashboard/actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"use server";

import { revalidatePath } from "next/cache";

import { getDatabaseClient, getDumpUrl } from "@/app/utils";
import * as schema from "@/db/schema";
import { getDatabaseClient } from "@/app/utils";
import { eq } from "drizzle-orm";

export type TodoItem = {
id: number;
Expand All @@ -16,9 +17,8 @@ export const addTodo = async (formData: FormData) => {

if (!client) return null;

await client.execute({
sql: "INSERT INTO todos (description) VALUES (?)",
args: [description],
await client.insert(schema.todos).values({
description,
});

revalidatePath("/dashboard");
Expand All @@ -29,10 +29,7 @@ export const removeTodo = async (id: number) => {

if (!client) return null;

await client.execute({
sql: "DELETE FROM todos WHERE id = ?",
args: [id],
});
await client.delete(schema.todos).where(eq(schema.todos.id, id));

revalidatePath("/dashboard");
};
7 changes: 5 additions & 2 deletions app/(authenticated)/dashboard/todo.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"use client";

import { useTransition } from "react";
import { InferSelectModel } from "drizzle-orm";
import * as schema from "@/db/schema";
import { removeTodo } from "./actions";

import { removeTodo, type TodoItem } from "./actions";
type Todo = InferSelectModel<typeof schema.todos>;

export function Todo({ item }: { item: TodoItem }) {
export function Todo({ item }: { item: Todo }) {
const [_, startTransition] = useTransition();

return (
Expand Down
9 changes: 3 additions & 6 deletions app/(authenticated)/dashboard/todos.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { getDatabaseClient } from "@/app/utils";

import { Todo } from "./todo";
import { type TodoItem } from "./actions";

export async function Todos() {
const client = await getDatabaseClient();
Expand All @@ -10,13 +9,11 @@ export async function Todos() {
return <p>No such table `todos`</p>;
}

const result = await client.execute("SELECT * FROM todos");
const todos = await client.query.todos.findMany();

const rows = result.rows as unknown as Array<TodoItem>;
if (!todos || todos.length === 0) return null;

if (!rows || rows.length === 0) return null;

return rows.map((todo, index) => (
return todos.map((todo, index) => (
<Todo key={index} item={{ id: todo.id, description: todo.description }} />
));
}
16 changes: 12 additions & 4 deletions app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { createClient as createLibsqlClient } from "@libsql/client";
import { createClient as createTursoClient } from "@tursodatabase/api";
import md5 from "md5";
import { redirect } from "next/navigation";
import { drizzle } from "drizzle-orm/libsql";

import * as schema from "@/db/schema";

const turso = createTursoClient({
token: process.env.TURSO_USER_API_TOKEN!,
Expand Down Expand Up @@ -46,10 +49,13 @@ export async function getDatabaseClient() {
}

try {
return createLibsqlClient({
url,
authToken: process.env.TURSO_DATABASE_GROUP_AUTH_TOKEN || "",
});
return drizzle(
createLibsqlClient({
url,
authToken: process.env.TURSO_DATABASE_GROUP_AUTH_TOKEN,
}),
{ schema },
);
} catch (error) {
console.error("Failed to create database client:", error);
return null;
Expand All @@ -58,7 +64,9 @@ export async function getDatabaseClient() {

export function getDatabaseName() {
const userId = auth().userId;

if (!userId) return null;

return md5(userId);
}

Expand Down
11 changes: 11 additions & 0 deletions db/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";

import * as schema from "./schema";

const turso = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
});

export const db = drizzle(turso, { schema });
8 changes: 8 additions & 0 deletions db/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { text, integer, sqliteTable } from "drizzle-orm/sqlite-core";

export const todos = sqliteTable("todos", {
id: integer("id", {
mode: "number",
}).primaryKey({ autoIncrement: true }),
description: text("description").notNull(),
});
27 changes: 27 additions & 0 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import "dotenv/config";
import { defineConfig } from "drizzle-kit";

if (!process.env.TURSO_SCHEMA_DATABASE_NAME) {
throw new Error("TURSO_SCHEMA_DATABASE_NAME is missing");
}

if (!process.env.TURSO_ORG_NAME) {
throw new Error("TURSO_ORG_NAME is missing");
}

if (!process.env.TURSO_DATABASE_GROUP_AUTH_TOKEN) {
throw new Error("TURSO_DATABASE_GROUP_AUTH_TOKEN is missing");
}

const url = `libsql://${process.env.TURSO_SCHEMA_DATABASE_NAME}-${process.env.TURSO_ORG_NAME}.turso.io`;

export default defineConfig({
schema: "./db/schema.ts",
out: "./migrations",
dialect: "sqlite",
driver: "turso",
dbCredentials: {
url,
authToken: process.env.TURSO_DATABASE_GROUP_AUTH_TOKEN,
},
});
Loading

0 comments on commit 600101e

Please sign in to comment.