Skip to content

Commit

Permalink
feat: flesh out getting started guide/tutorial in README (#99)
Browse files Browse the repository at this point in the history
This also has some fixes/improvements to the basic & todo example to
make them work + add READMEs e.g. `DELETE` wasn't being handled
correctly in `Shape` 😱 @icehaunter I quickly added a test but didn't
really verify it was properly testing it if you could double-check.

---------

Co-authored-by: Ilia Borovitinov <[email protected]>
  • Loading branch information
KyleAMathews and icehaunter authored Jul 18, 2024
1 parent f170484 commit 6fdb1b2
Show file tree
Hide file tree
Showing 21 changed files with 1,517 additions and 860 deletions.
6 changes: 6 additions & 0 deletions .changeset/light-islands-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@electric-sql/next": patch
"@electric-sql/react": patch
---

chore: updated testing fixtures
5 changes: 5 additions & 0 deletions .changeset/poor-parrots-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@core/sync-service": patch
---

feat: include ElectricSQL version header
22 changes: 12 additions & 10 deletions .support/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
version: "3.1"
version: "3.3"
name: "electric_example-${PROJECT_NAME:-default}"

services:
postgres:
image: postgres:14-alpine
image: postgres:16-alpine
environment:
POSTGRES_DB: electric
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:
- "54321:5432"
- 54321:5432
volumes:
- ./postgres.conf:/etc/postgresql.conf:ro
- ./postgres.conf:/etc/postgresql/postgresql.conf:ro
tmpfs:
- /var/lib/postgresql/data
- /tmp
entrypoint:
- docker-entrypoint.sh
command:
- postgres
- -c
- config_file=/etc/postgresql.conf
- config_file=/etc/postgresql/postgresql.conf
extra_hosts:
- "host.docker.internal:host-gateway"

backend:
image: electricsql/next:example
image: electricsql/electric-next:example
environment:
DATABASE_URL: postgresql://postgres:password@host.docker.internal:54321/electric
DATABASE_URL: postgresql://postgres:password@postgres:5432/electric
ports:
- "3000:3000"
build:
context: ../packages/sync-service/
context: ../packages/sync-service/
depends_on:
- postgres
175 changes: 131 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,68 +10,100 @@ See James' blog post for more background on the change: https://next.electric-sq

## Getting Started

1. Install the TypeScript client and React integrations
`npm install @electric-sql/next @electric-sql/react`
#### Create a new React app

2. Run Docker Compose similar to the following to setup Postgres and Electric
`npm create vite@latest my-first-electric-app -- --template react-ts`

#### Setup Docker Compose to run Postgres and Electric

`docker-compose.yaml`

```docker
version: "3.8"
name: "todomvc"
configs:
postgres_config:
file: "./postgres/postgres.conf"
volumes:
pg_data:
name: "my-first-electric-service"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: electric
POSTGRES_USER: postgres
POSTGRES_PASSWORD: pg_password
POSTGRES_PASSWORD: password
ports:
- 55321:5432
volumes:
- ./postgres.conf:/etc/postgresql/postgresql.conf:ro
command:
- postgres
- -c
- config_file=/etc/postgresql.conf
configs:
- source: postgres_config
target: /etc/postgresql.conf
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
- config_file=/etc/postgresql/postgresql.conf
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- 5632:5432
volumes:
- pg_data:/var/lib/postgresql/data
# TODO add Electric image
electric:
image: electricsql/next:example
environment:
DATABASE_URL: postgresql://postgres:[email protected]:55321/electric
ports:
- "3000:3000"
build:
context: ~/programs/electric-next/packages/sync-service/
```

Add a `postgresql.conf` file.
Add a `postgres.conf` file.

```
listen_addresses = '*'
wal_level = 'logical'
```

3. Try a curl command
`curl http://localhost:3000/v1/shape/{table}`
#### Start Docker

`docker compose -f ./docker-compose.yaml up`

#### Try a curl command against Electric's HTTP API

`curl -i http://localhost:3000/v1/shape/foo?offset=-1`

This request asks for a shape composed of the entire `foo` table.

A bit of explanation about the URL structure — `/v1/shape/` are standard
segments. `foo` is the name of the root table of the shape (and is required).
`offset=-1` means we're asking for the entire log of the Shape as we don't have
any of the log cached locally yet. If we had previously fetched the shape and
wanted to see if there was any updates, we'd set the offset of the last log
message we'd got the first time.

You should get a response like this:

```bash
HTTP/1.1 400 Bad Request
date: Wed, 17 Jul 2024 20:30:31 GMT
content-length: 62
vary: accept-encoding
cache-control: max-age=0, private, must-revalidate
x-request-id: F-MaJcF9A--cg9QAAAeF
access-control-allow-origin: *
access-control-expose-headers: *
access-control-allow-methods: GET, POST, OPTIONS
Server: ElectricSQL/0.0.1
content-type: application/json; charset=utf-8

{"offset":["can't be blank"],"root_table":["table not found"]}%
```

Start Docker: `docker compose -f ./docker-compose.yaml up`
So it didn't work! Which makes sense... as it's a empty database without any tables or data. Let's fix that.

3. Create a table and insert some data:
#### Create a table and insert some data

Use your favorite Postgres client to connect to Postgres e.g. with [psql](https://www.postgresql.org/docs/current/app-psql.html)
you run: `psql postgresql://postgres:password@localhost:55321/electric`

```sql
CREATE TABLE foo (
id INT PRIMARY KEY AUTO_INCREMENT, -- Unique identifier, auto-incrementing
name VARCHAR(255), -- Text field for names (adjust size as needed)
value FLOAT -- Numeric value (can be decimal)
id SERIAL PRIMARY KEY,
name VARCHAR(255),
value FLOAT
);

INSERT INTO foo (name, value) VALUES
Expand All @@ -82,37 +114,92 @@ INSERT INTO foo (name, value) VALUES
('Eve', 0);
```

4. Try a curl command to Electric's HTTP API:
#### Now try the curl command again

`curl http://localhost:3000/shape/foo?offset=-1`

Success! You should see the data you just put into Postgres in the shape response:

```bash
HTTP/1.1 200 OK
date: Wed, 17 Jul 2024 20:38:07 GMT
content-length: 643
vary: accept-encoding
cache-control: max-age=60, stale-while-revalidate=300
x-request-id: F-Maj_CikDKfZTIAAAAh
access-control-allow-origin: *
access-control-expose-headers: *
access-control-allow-methods: GET, POST, OPTIONS
Server: ElectricSQL/0.0.1
content-type: application/json; charset=utf-8
x-electric-shape-id: 3833821-1721248688126
x-electric-chunk-last-offset: 0_0
etag: 3833821-1721248688126:-1:0_0

[{"offset":"0_0","value":{"id":1,"name":"Alice","value":3.14},"key":"\"public\".\"foo\"/1","headers":{"action"
:"insert"}},{"offset":"0_0","value":{"id":2,"name":"Bob","value":2.71},"key":"\"public\".\"foo\"/2","headers":
{"action":"insert"}},{"offset":"0_0","value":{"id":3,"name":"Charlie","value":-1.618},"key":"\"public\".\"foo\
"/3","headers":{"action":"insert"}},{"offset":"0_0","value":{"id":4,"name":"David","value":1.414},"key":"\"pub
lic\".\"foo\"/4","headers":{"action":"insert"}},{"offset":"0_0","value":{"id":5,"name":"Eve","value":0.0},"key
":"\"public\".\"foo\"/5","headers":{"action":"insert"}},{"headers":{"control":"up-to-date"}}]%
```
#### Now let's fetch the same shape to use in our React app
Install the Electric React package:
`npm install @electric-sql/react`
`curl http://localhost:3000/shape/foo`
5. Add to React app
Wrap your root in `src/main.tsx` with the `ShapesProvider`:
Add the Shapes provider
```tsx
import { ShapesProvider } from "@electric-sql/react"
ReactDOM.createRoot(document.getElementById(`root`)!).render(
<ShapesProvider>
<App />
</ShapesProvider>
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ShapesProvider>
<App />
</ShapesProvider>
</React.StrictMode>,
)
```
Add `useShape` to a component
Replace `App.tsx` with the following:
```tsx
import { useShape } from "@electric-sql/react"
import { useShape } from "@electric-sql/react";
function Component {
function Component() {
const { data: fooData } = useShape({
shape: { table: `foo` },
baseUrl: `http://localhost:3000`,
})
});
return JSON.stringify(foo, null, 4)
return JSON.stringify(fooData, null, 4);
}
export default Component;
```
Finally run the dev server to see it all in action!
`npm run dev`
You should see something like:
<img width="699" alt="Screenshot 2024-07-17 at 2 49 28 PM" src="https://github.com/user-attachments/assets/cda36897-2db9-4f6c-86bb-99e7e325a490">
#### Postgres as a real-time database
Go back to your postgres client and update a row. It'll instantly be synced to your component!
```sql
UPDATE foo SET name = 'James' WHERE id = 2;
```
Congradulations! You've now built your first Electric app!
## HTTP API Documentation
The HTTP API documentation is defined through an OpenAPI 3.1.0 specification found in `docs/electric-api.yaml`. Documentation for the API can be generated with `npm run docs:generate`.
Expand Down
23 changes: 22 additions & 1 deletion examples/basic-example/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
# TODO
# Basic example

## Setup

1. Make sure you've installed all dependencies for the monorepo and built packages

From the root directory:

- `pnpm i`
- `pnpm run -r build`

2. Start the docker containers

`pnpm run backend:up`

3. Start the dev server

`pnpm run dev`

4. When done, tear down the backend containers so you can run other examples

`pnpm run backend:down`
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ CREATE TABLE IF NOT EXISTS items (
id TEXT PRIMARY KEY NOT NULL
);


-- Populate the table with 10 items.
-- FIXME: Remove this once writing out of band is implemented
WITH generate_series AS (
Expand Down
12 changes: 6 additions & 6 deletions examples/basic-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
},
"dependencies": {
"@electric-sql/react": "workspace:*",
"react": "^18.3.0",
"react-dom": "^18.3.0"
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@databases/pg-migrations": "^5.0.3",
"@types/react": "^18.3.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0",
"@vitejs/plugin-react": "^4.3.1",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"typescript": "^5.4.5",
"vite": "^5.2.12"
"typescript": "^5.5.3",
"vite": "^5.3.4"
}
}
8 changes: 4 additions & 4 deletions examples/basic-example/src/Example.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { useShape } from '@electric-sql/react'

import './Example.css'

type Item = { id: string }

const baseUrl = import.meta.env.ELECTRIC_URL ?? `http://localhost:3000`
//const { useShape } = reactHooks

export const Example = () => {
const { data: items } = useShape({
shape: { table: `items` },
baseUrl,
}) as unknown as { data: Item[] }

/*
const addItem = async () => {
console.log(`'addItem' is not implemented`)
}
Expand All @@ -21,8 +20,6 @@ export const Example = () => {
console.log(`'clearItems' is not implemented`)
}
return (
<div>
<div className="controls">
<button className="button" onClick={addItem}>
Add
Expand All @@ -31,6 +28,9 @@ export const Example = () => {
Clear
</button>
</div>
*/
return (
<div>
{items.map((item: Item, index: number) => (
<p key={index} className="item">
<code>{item.id}</code>
Expand Down
Loading

0 comments on commit 6fdb1b2

Please sign in to comment.