Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4528a4e
Manage tool versions with mise
lifeiscontent Oct 6, 2026
41bac98
Upgrade Storybook to 10 and storybook-addon-apollo-client to 11
lifeiscontent Oct 6, 2026
a4b3a1e
Paginate articles with limit and offset
lifeiscontent Oct 6, 2026
649ef31
Create articles with tag names
lifeiscontent Oct 6, 2026
8f8ea26
Upgrade the API to Ruby 4.0.7 and Rails 8.1
lifeiscontent Oct 6, 2026
d02d2f4
Sign JWTs with the Rails secret_key_base
lifeiscontent Oct 6, 2026
bc4dfd0
Upgrade the API to Postgres 18 and pin CI actions
lifeiscontent Oct 6, 2026
f4ad863
Fix the RuboCop offenses from RuboCop 1.91
lifeiscontent Oct 6, 2026
8289b7a
Show all tags in use as popular tags
lifeiscontent Oct 6, 2026
f427d43
Map the GraphQL API one to one to the RealWorld API
lifeiscontent Oct 6, 2026
a6502d9
Clean up and modernize the API
lifeiscontent Oct 7, 2026
32bbb4f
Rewrite web as a Vite app with React Router 8 and Apollo Client 4
lifeiscontent Oct 7, 2026
3fde898
Run the RealWorld e2e suite against the web app
lifeiscontent Oct 7, 2026
83c61b4
Add stories and unit tests for the web app
lifeiscontent Oct 7, 2026
932f43a
Rewrite the web feature development guide
lifeiscontent Oct 7, 2026
27f535a
Update CI, Dependabot, and the README for the new stack
lifeiscontent Oct 7, 2026
d64143b
Split web components by role and add layout routes
lifeiscontent Oct 7, 2026
14bb7cc
Move web to React Router framework mode in SPA mode
lifeiscontent Oct 7, 2026
0a70495
Start the API for the web e2e job without the deleted database file
lifeiscontent Oct 7, 2026
044a1af
Keep RuboCop's default excludes in the API config
lifeiscontent Oct 7, 2026
3fa6afd
Do not seed the test database
lifeiscontent Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @lifeiscontent
39 changes: 31 additions & 8 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,44 @@
version: 2
updates:
- package-ecosystem: bundler
directory: "/api"
directory: /api
schedule:
interval: daily
groups:
rails:
patterns:
- "rails"
- "action*"
- "active*"
- "railties"
open-pull-requests-limit: 10
reviewers:
- lifeiscontent
- package-ecosystem: npm
directory: "/web"
directory: /web
schedule:
interval: daily
groups:
storybook:
applies-to: version-updates
patterns:
- "@storybook*"
- "storybook"
- "@storybook/*"
- "eslint-plugin-storybook"
graphql-codegen:
patterns:
- "@graphql-codegen/*"
vitest:
patterns:
- "vitest"
- "@vitest/*"
playwright:
patterns:
- "playwright"
- "@playwright/*"
oxc:
patterns:
- "oxlint"
- "oxfmt"
open-pull-requests-limit: 10
reviewers:
- lifeiscontent
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
42 changes: 20 additions & 22 deletions .github/workflows/api.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,39 @@
name: Api

on: [push]
on:
push:
branches: [main]
paths: ['api/**', '.github/workflows/api.yml']
pull_request:
paths: ['api/**', '.github/workflows/api.yml']

jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:12
image: postgres:18
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/realworld_test
defaults:
run:
working-directory: ./api
steps:
- uses: actions/checkout@v4
- name: Install dependent libraries
run: sudo apt-get install libpq-dev
- uses: ruby/setup-ruby@v1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
working-directory: ./api
bundler-cache: true
- name: Setup Database
- name: Run setup, RuboCop, and RSpec
run: bin/ci
- name: Check that schema.graphql is current
run: |
cp config/database.yml.github-actions config/database.yml
bin/rails db:create
bin/rails db:schema:load
working-directory: ./api
env:
RAILS_ENV: test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
- name: Run RSpec
run: bundle exec rspec
working-directory: ./api
env:
RAILS_ENV: test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
bin/rails graphql:schema:idl
git diff --exit-code schema.graphql
94 changes: 69 additions & 25 deletions .github/workflows/web.yml
Original file line number Diff line number Diff line change
@@ -1,37 +1,81 @@
name: Web

on: [push]
on:
push:
branches: [main]
paths: ['web/**', 'api/**', '.github/workflows/web.yml']
pull_request:
paths: ['web/**', 'api/**', '.github/workflows/web.yml']

defaults:
run:
working-directory: ./web

jobs:
build:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: actions/setup-node@v2
- uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
with:
cache-dependency-path: web/package-lock.json
cache: "npm"
node-version: "18.x"
- run: npm install
working-directory: ./web
- run: npm run lint
working-directory: ./web
- run: npm run build
working-directory: ./web
- run: npx playwright install --with-deps
working-directory: ./web
- run: npm run build-storybook -- --quiet
working-directory: ./web
- run: |
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --port 6006 --silent" \
"npx wait-on http://127.0.0.1:6006 && npm run test-storybook"
working-directory: ./web
working_directory: web
- run: pnpm install --frozen-lockfile
- run: pnpm format:check
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm exec playwright install --with-deps chromium
- name: Run the stories as tests
run: pnpm test
- run: pnpm build
- name: Publish Storybook and run visual tests
if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]'
run: npm run chromatic -- --exit-once-uploaded
if: github.actor != 'dependabot[bot]'
run: pnpm chromatic --exit-once-uploaded
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
working-directory: ./web

e2e:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/realworld_test
TEST_MODE: fullstack
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
working-directory: ./api
bundler-cache: true
- name: Start the API on port 4000
working-directory: ./api
run: |
bin/rails db:create db:schema:load
bin/rails server -p 4000 > log/server.log 2>&1 &
for _ in $(seq 1 30); do
curl --silent --fail http://localhost:4000/up > /dev/null && exit 0
sleep 1
done
cat log/server.log
exit 1
- uses: jdx/mise-action@2d8d4cafcbd33be2ea37d2b6f5ad595363d1f1ca # v5.1.1
with:
working_directory: web
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
- name: Run the RealWorld e2e suite
run: pnpm exec playwright test
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
with:
name: playwright-results
path: web/test-results
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["oxc.oxc-vscode", "Shopify.ruby-lsp", "GraphQL.vscode-graphql"]
}
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

62 changes: 35 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,71 @@
# realworld

![Web](https://github.com/lifeiscontent/realworld/workflows/Web/badge.svg?branch=master)
![Api](https://github.com/lifeiscontent/realworld/workflows/Api/badge.svg?branch=master)
[![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@master/badge/badge-storybook.svg)](https://master--5fcdd27b2771900021fc381e.chromatic.com)
![Web](https://github.com/lifeiscontent/realworld/actions/workflows/web.yml/badge.svg?branch=main)
![Api](https://github.com/lifeiscontent/realworld/actions/workflows/api.yml/badge.svg?branch=main)
[![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@master/badge/badge-storybook.svg)](https://main--5fcdd27b2771900021fc381e.chromatic.com)

This repo implements opinionated best practices for Apollo Client, React, Next.js and Storybook.
A [RealWorld](https://docs.realworld.show) app with a GraphQL API. The
GraphQL schema maps one to one to the RealWorld REST API.

1 caveat is it does not try to implement a best practice on UI Components as the styles are reused from the [realworld.io](https://github.com/gothinkster/realworld) project.
| Project | Stack |
| --- | --- |
| [`api`](api) | Ruby 4, Rails 8.1, graphql-ruby, action_policy, Postgres 18 |
| [`web`](web) | TypeScript, Vite, React 19, React Router 8 (SPA mode), Apollo Client 4, Storybook 10 |

The two projects deploy independently. Each project has its own
`mise.toml`, so [mise](https://mise.jdx.dev) installs the correct tool
versions in each folder.

## Setup

### Api

The API needs Postgres. Set `DATABASE_URL` if Postgres is not on
`localhost:5432`.

```sh
cd api
bundle
bin/rails db:migrate
bin/rails db:seed
bin/rails s
mise install
bin/setup
```

`bin/setup` installs the gems, prepares the database, and starts the API on
port 4000.

### Web

```sh
cd web
npm install
npm run dev
mise install
pnpm install
pnpm dev
```

## Testing
The app opens on <http://localhost:5173> and uses the API on port 4000.

## Tests

### Api

```sh
cd api
bin/rails spec
bundle exec rspec
```

### Web

```sh
cd web
npm test
pnpm test
```

## Tooling

### Api

#### Generate ERD
`pnpm test` runs the Storybook stories as browser tests. To run the
RealWorld e2e suite, start the API, then run:

```sh
bin/rails erd
cd web
TEST_MODE=fullstack pnpm exec playwright test
```

### Web

#### Storybook

```sh
npm run storybook
```
See [web/docs/feature-development.md](web/docs/feature-development.md) for
the structure of the web app.
22 changes: 0 additions & 22 deletions api/.erdconfig

This file was deleted.

6 changes: 0 additions & 6 deletions api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,5 @@
!/tmp/pids/
!/tmp/pids/.keep

# Ignore uploaded files in development.
/storage/*
!/storage/.keep
.byebug_history

# Ignore master key for decrypting credentials and more.
/config/master.key
/erd.pdf
Loading
Loading