はじめに
個人開発でWebアプリを作ろうとすると、決めなければいけないことが多すぎます。フロントエンドはReact、APIはどう立てる、ORMは何を使う、認証はどう組む、テストとStorybookの設定は……。1つ1つは選べても、それらを矛盾なく配線し続けるのが地味に重労働です。
RedwoodJSは、この「配線」を最初からまとめて用意してくれるフルスタックフレームワークです。React・GraphQL・Prisma・TypeScript・Jest・Storybookをあらかじめ統合し、公式が掲げる「サイドプロジェクトからスタートアップへ育てる」というコンセプトどおり、小さく始めて大きくできる構成になっています。
この記事ではRedwoodJSの特徴と基本的な使い方、実践的なユースケースを見ていきます。
RedwoodJSとは
RedwoodJSは、Reactによるフロントエンド(web側)とGraphQL APIによるバックエンド(api側)を1つのモノレポで管理するフルスタックフレームワークです。裏側のデータベースアクセスにはPrismaを採用し、スキーマ定義からマイグレーション、型安全なクエリまでを一貫して扱えます。
もともとはNetlifyやVercelなどへのデプロイを想定した構成でしたが、開発チームは現在、Cloudflare上でReact Server Componentsを動かす姉妹プロジェクト「RedwoodSDK」も展開しています。本記事で扱うのは、api/web構成とGraphQL・PrismaによるクラシックなRedwoodJS(現行の安定版は "Arapahoe" 系列)です。
主な特徴
- api/webのモノレポ構成 -
apiディレクトリにGraphQLサーバーとビジネスロジック、webディレクトリにReactアプリを置き、1つのリポジトリ・1つのyarn redwood devコマンドで両方を同時に開発できます - Cellsによる宣言的なデータ取得 -
Loading/Empty/Failure/Successの4状態をコンポーネントとして定義するだけで、GraphQLのローディング・エラー処理を自動的に組み込めます - Prismaベースのデータベース層 -
schema.prismaを1つ書けば、マイグレーションとGraphQLのSDL・リゾルバの雛形までコマンド1つで生成されます - 標準搭載のテスト・Storybook環境 - JestとStorybookが最初から設定済みで、コンポーネントとAPIの両方に対してテストコードの雛形を生成できます
- 柔軟な認証プロバイダ - Auth0、Clerk、Supabase、dbAuthなど複数の認証方式をコマンド1つで組み込めます
インストール
Node.jsとYarnが入っている環境で、以下のコマンドを実行するとプロジェクトが作成されます。
yarn create redwood-app my-redwood-app
TypeScriptで始めたい場合は--typescriptフラグを付けます(デフォルトでもTypeScriptテンプレートが選べます)。
yarn create redwood-app my-redwood-app --typescript
作成後はディレクトリに移動して依存関係をインストールし、開発サーバーを起動します。
cd my-redwood-app
yarn install
yarn redwood dev
yarn redwood devを実行すると、http://localhost:8910でwebアプリが、http://localhost:8911でGraphQL APIサーバー(GraphQL Playground付き)が同時に立ち上がります。
基本的な使い方
RedwoodJSの開発は、まずPrismaでデータモデルを定義するところから始まります。api/db/schema.prismaに以下のようなモデルを書きます。
// api/db/schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
body String
createdAt DateTime @default(now())
}
マイグレーションを実行してデータベースに反映させます。
yarn redwood prisma migrate dev
ここまでできたら、generateコマンドでGraphQLのSDL・サービス(リゾルバ)・ページ・コンポーネント・Cellなどの雛形をまとめて作れます。CRUD一式を一括生成するscaffoldコマンドも用意されています。
yarn redwood generate scaffold post
このコマンド1つで、投稿の一覧・詳細・作成・編集・削除ができるページとGraphQL API、そのためのテストコードまでが生成されます。RedwoodJSが「配線を肩代わりする」と言われる所以は、このスキャフォールディングの守備範囲の広さにあります。
実践的なユースケース
RedwoodJSには、データ取得・データ更新・認証まわりでそれぞれ定番の書き方があります。ここでは代表的な3パターンを見ていきます。
Cellsで宣言的にデータを取得する
一覧画面を作るとき、ローディング中の表示・データが0件のときの表示・エラー時の表示・成功時の表示をいちいちif文で分岐して書くのは面倒です。RedwoodJSのCellsは、この4状態をエクスポートするだけで、GraphQLクエリの実行からハンドリングまでを自動化してくれます。
// web/src/components/PostsCell/PostsCell.jsx
export const QUERY = gql`
query PostsQuery {
posts {
id
title
}
}
`
export const Loading = () => <div>読み込み中...</div>
export const Empty = () => <div>投稿がありません</div>
export const Failure = ({ error }) => <div>エラー: {error.message}</div>
export const Success = ({ posts }) => (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
QUERYで定義したGraphQLクエリの結果に応じて、RedwoodJSが自動的にLoading → Success(またはEmpty・Failure)を出し分けます。呼び出す側は<PostsCell />と書くだけで、状態管理のコードを自前で書く必要がありません。
GraphQLミューテーションでデータを更新する
投稿の作成・更新・削除のような書き込み処理は、useMutationフックでGraphQLのミューテーションを呼び出す形で書きます。
// web/src/components/NewPost/NewPost.jsx
import { useMutation } from '@redwoodjs/web'
const CREATE_POST = gql`
mutation CreatePostMutation($input: CreatePostInput!) {
createPost(input: $input) {
id
}
}
`
const NewPost = () => {
const [createPost, { loading, error }] = useMutation(CREATE_POST, {
onCompleted: () => {
window.location.href = '/posts'
},
})
const onSubmit = (data) => {
createPost({ variables: { input: data } })
}
return (
<div>
{error && <div>投稿に失敗しました: {error.message}</div>}
<button disabled={loading} onClick={() => onSubmit({ title: '新しい投稿', body: '本文' })}>
投稿する
</button>
</div>
)
}
export default NewPost
useMutationは@redwoodjs/webが提供するApollo Client由来のフックで、loadingとerrorの状態がそのまま返ってくるため、送信中の非活性表示やエラー表示をすぐに実装できます。
dbAuthで認証を組み込む
自前のデータベースだけで認証を完結させたい場合は、dbAuthというRedwoodJS標準の認証プロバイダを使います。セットアップコマンドを実行すると、ユーザーテーブルのマイグレーションとログイン・サインアップページの雛形が一括生成されます。
yarn redwood setup auth dbAuth
生成されたコードを使うと、ページ側では認可チェックをこのように書けます。
// web/src/Routes.jsx
import { Router, Route, Private } from '@redwoodjs/router'
const Routes = () => (
<Router>
<Route path="/login" page={LoginPage} name="login" />
<Private unauthenticated="login">
<Route path="/admin/posts" page={AdminPostsPage} name="adminPosts" />
</Private>
<Route notfound page={NotFoundPage} />
</Router>
)
export default Routes
<Private>でルートを囲むだけで、未ログインユーザーは自動的にloginルートへリダイレクトされます。Auth0やClerk、Supabaseなど外部プロバイダに切り替えたい場合も、yarn redwood setup authのオプションを変えるだけで対応できます。
まとめ
RedwoodJSは、React・GraphQL・Prisma・TypeScript・Jest・Storybookという定番の組み合わせを、あらかじめ矛盾なく配線した状態で提供してくれるフルスタックフレームワークです。Cellsによる宣言的なデータ取得、scaffoldコマンドによるCRUD一式の自動生成、dbAuthをはじめとする認証プロバイダの切り替えやすさなど、個人開発のスピード感を保ったままチーム開発やスタートアップ規模まで育てられる設計になっています。
まずはyarn create redwood-appでプロジェクトを作り、yarn redwood generate scaffoldで1つモデルを作ってみると、RedwoodJSがどこまで面倒を見てくれるかが体感できるはずです。