Skip to content

Commit

Permalink
fix(C5): improve chapter 5 (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
uhasker authored Dec 15, 2024
1 parent 5ed2b70 commit 614f310
Show file tree
Hide file tree
Showing 7 changed files with 710 additions and 186 deletions.
6 changes: 4 additions & 2 deletions src/chapter1/01-hello-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ The filename should have an _html_ extension though.
Open the HTML file in any text editor (see below for text editors that are good choices for coding) and add the following text to the file:

```html
<!doctype html>
<!DOCTYPE html>
<html>
<head></head>
<body>
Expand Down Expand Up @@ -319,4 +319,6 @@ Waiting...
Waiting...

_Finally_.
Let's move on.

Additionally, **you should absolutely type the code yourself and not copy and paste it**.
If you just copy and paste the code, you will learn absolutely nothing and may as well not read this book at all.
142 changes: 116 additions & 26 deletions src/chapter5/01-setup.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,158 @@
## Setup

<div style="text-align: right"> <i> Astro DB is powered by Drizzle! <br> ... and we regret everything omg this thing sucks <br> - From Drizzles official page </i> </div>
<div style="text-align: right"> <i> Astro DB is powered by Drizzle! <br> ... and we regret everything omg this thing sucks <br> From Drizzles' official marketing page </i> </div>

### A Simple Example

Let's create a simple Drizzle script that will declare a task table and read all the tasks from the table.
Create a new Supabase database and recreate the `task` table from the SQL chapter:

Create a new supabase database and recreate the task table from the SQL chapter.
Don't add the project IDs and table yet, that will follow later.
```sql
create type status as enum ('todo', 'inprogress', 'done');

create table task (
id serial primary key,
title text not null unique,
description text not null,
status status,
duration integer check (duration > 0),
created_at timestamp default current_timestamp not null
);
```

Let's also insert some values into the `task` table:

Create a new TypeScript project:
```sql
insert into task (title, description, duration, status) values
('Read the Next.js book', 'Read and understand the Next.js book.', 60, 'inprogress'),
('Write a task app', 'Write an awesome task app.', 10, 'todo'),
('Think of a funny joke', 'Come up with a funny joke to lighten the mood.', 120, 'inprogress');
```

Don't add the `project` table yet, that will come later.

Before we can write code, we need to setup Drizzle.

First, we have to initialize a new TypeScript project and also add a `tsconfig.json` file.
You already know the relevant commands from the TypeScript chapter:

```sh
pnpm init
pnpm add typescript tsx --save-dev
pnpm tsc --init
```

Install Drizzle:
Next, we have to install Drizzle.
The core package is called `drizzle-orm`.
We will also need the `pg` package to be able to interact with our PostgreSQL database.

```sh
pnpm add drizzle-orm pg
```

We will also need the `@types/pg` package to get the type definitions for `pg`:

```sh
pnpm add drizzle-orm postgres
pnpm add drizzle-kit --save-dev
pnpm add @types/pg --save-dev
```

Create the following file `demo.ts`:

```ts
import { pgTable, serial, text, integer, timestamp, varchar } from 'drizzle-orm/pg-core';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import {
pgTable,
pgEnum,
serial,
text,
integer,
timestamp,
varchar,
check,
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/node-postgres';

// Paste the supabase URI here
const databaseURI = '...';

// Declare the task table
const taskTable = pgTable('task', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
description: text('description').notNull(),
status: varchar('status', { length: 255 }).notNull(),
duration: integer('duration'),
createdAt: timestamp('created_at').defaultNow().notNull(),
});

const client = postgres(databaseURI);
const db = drizzle(client);
const statusEnum = pgEnum('status', ['todo', 'inprogress', 'done']);

// Declare the task table
const taskTable = pgTable(
'task',
{
id: serial('id').primaryKey(),
title: text('title').notNull(),
description: text('description').notNull(),
status: statusEnum().notNull(),
duration: integer('duration'),
createdAt: timestamp('created_at').defaultNow().notNull(),
},
(table) => [
{
durationCheckConstraint: check('duration_check', sql`${table.duration} > 0`),
},
],
);

// Connect Drizzle to the database
const db = drizzle(databaseURI);

// Execute a query
async function getTasks() {
return await db.select().from(taskTable);
}

getTasks().then(console.log);
```

> Note that the `check` constraint is not yet implemented in Drizzle at the time of this writing.
> Note that hardcoding passwords and secrets in code is really poor style.
> We will fix this at the end of this chapter.
There are two really important functions that Drizzle provides for you.

First, we have the `pgTable` function.
This declares a table schema and allows you to map the underneath SQL table to JavaScript (or TypeScript) objects.

Second, we have the `drizzle` function.
This allows you to establish the actual connection to the database and serves as the entry point for all your database interactions.
It returns a `db` object that represents the Drizzle client and allows you execute the actual queries (like `db.select().from(taskTable)`).

Execute the file:

```sh
pnpm tsx demo.ts
```

You will see a list of all the tasks that are currently present in the table.
You will see a list of all the tasks that are currently present in the table:

```json
[
{
"id": 1,
"title": "Read the Next.js book",
"description": "Read and understand the Next.js book.",
"status": "inprogress",
"duration": 60,
"createdAt": "2024-12-15T10:49:46.049Z"
},
{
"id": 2,
"title": "Write a task app",
"description": "Write an awesome task app.",
"status": "todo",
"duration": 10,
"createdAt": "2024-12-15T10:49:46.049Z"
},
{
"id": 3,
"title": "Think of a funny joke",
"description": "Come up with a funny joke to lighten the mood.",
"status": "inprogress",
"duration": 120,
"createdAt": "2024-12-15T10:49:46.049Z"
}
]
```

### Drizzle as Typesafe SQL

Expand All @@ -79,7 +169,7 @@ The SQL function was:
select * from task;
```

This similarity is _intentional_ and will be a major theme in this chapter.
This similarity is quite intentional and will be a major theme in this chapter.
Unlike many other frameworks which try to "abstract" SQL away, Drizzle embraces SQL and only adds a bit of type safety on top of it.

If you know SQL, learning Drizzle is a very fast process.
45 changes: 28 additions & 17 deletions src/chapter5/02-inserting-updating-and-deleting-data.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
## Inserting, Updating and Deleting Data

<div style="text-align: right"> <i> Django had it in 2008 <br> - From Drizzles official page </i> </div>
<div style="text-align: right"> <i> Django had it in 2008 <br> From Drizzles' official marketing page </i> </div>

### Inserting Data

Inserting data generally looks like this:
To insert data in SQL, you use the `insert` statement.
In Drizzle, you can use the appropriately named `insert` function on the `db` object:

```ts
await db.insert(table).values(values);
```

Here is how you would insert a row into `taskTable`:
You need to declare the table to write to and the dictionary containing the columns and the values to insert into the respective columns.

For example, here is how you would insert a row into `taskTable`:

```ts
await db.insert(taskTable).values({
Expand All @@ -21,27 +24,23 @@ await db.insert(taskTable).values({
});
```

You can insert a row and get it back:
You can also insert a row and get it back using the `returning` function.
This is useful if you want to get the data that has been automatically inserted into the database (like an ID):

```ts
const row = await db
.insert(taskTable)
.values({
name: 'Example project',
title: 'Read the Next.js book',
description: 'Read and understand the Next.js book.',
status: 'inprogress',
duration: 60,
})
.returning();
console.log(row);
```

This would output something like:

```
[ { id: 3, name: 'Example project' } ]
console.log(row.id); // Will output the ID of the resulting row
```

The `returning` function is mostly useful if you want to get the ID of the inserted row.

You can insert multiple rows by providing an array of objects:
You can insert multiple rows at the same by providing an array of objects:

```ts
await db.insert(taskTable).values([
Expand All @@ -68,26 +67,38 @@ await db.insert(taskTable).values([

### Updating Data

Updating data generally looks like this:
To update data in SQL, you use the `update` statement.
In Drizzle, you can use the appropriately named `update` function on the `db` object:

```ts
await db.update(table).set(object).where(condition);
```

You need to specify the table to update, the columns with the values to update, and a condition for which rows to update.

For example, let's say that wanted to set status of the task with the ID `1` to `'done'`:

```ts
await db.update(taskTable).set({ status: 'done' }).where(eq(taskTable.id, 1));
```

Just as with SQL, you can update more than one column at the same time:

```ts
await db.update(taskTable).set({ status: 'done', duration: 0 }).where(eq(taskTable.id, 1));
```

### Deleting Data

Updating data generally looks like this:
To delete data in SQL, you use the `delete` statement.
In Drizzle, you can use the appropriately named `delete` function on the `db` object:

```ts
await db.delete(table).where(condition);
```

You need to specify the table to delete data from as well as a condition that specifies what data to delete.

For example, here is how you could delete all the completed tasks:

```ts
Expand Down
Loading

0 comments on commit 614f310

Please sign in to comment.