はじめに
管理画面やダッシュボードの開発、正直なところ面倒くさいと思ったことないですか。
CRUDの実装、認証、権限管理、ルーティング…毎回同じようなコードを書いている気がして、30代になってからは「この時間、もっと有意義に使いたいな」と思うようになってきたんですよね。
そんな時に出会ったのがRefineというReactフレームワーク。GitHubスター数33,000以上、世界中で8,000以上のプロダクションプロジェクトで使われているという実績。個人的には、管理画面開発においてはこれ一択ですねと言いたくなるレベルでした。
Refineは普段バックエンドAPIと繋いで使いますが、dataProviderをインメモリのモック実装に差し替えれば、ブラウザだけでCRUDの挙動を再現できます。先に触ってみたい方はこちらからどうぞ。
Refineとは
Refineは、内部ツール・管理パネル・ダッシュボード・B2Bアプリケーションを構築するためのReactメタフレームワークです。
「Open-source Retool for Enterprise」を謳っていて、ローコードツールの手軽さと、フルコード開発の柔軟性を両立させているのが特徴。企業向けのCRUD集約型アプリケーション開発に特化していて、認証・アクセス制御・ルーティング・ネットワーク処理・状態管理・多言語対応といった面倒な部分を標準で提供してくれます。
特徴・メリット
ヘッドレスアーキテクチャ
これ、意外と重要なポイントなんですよ。RefineはUIに依存しないヘッドレス設計になっているので、好きなUIフレームワークと組み合わせられます。
- Ant Design
- Material UI
- Mantine
- Chakra UI
- shadcn/ui
- Tailwind CSS
既存のデザインシステムがある会社でも、無理なく導入できるのがありがたい。
幅広いバックエンド対応
15以上のバックエンドサービスと統合できます。
- Firebase
- Supabase
- Strapi
- Hasura
- Appwrite
- REST API
- GraphQL
データソースが何であっても、同じ書き方でアプリケーションを構築できるのは開発効率的にかなり助かります。
エンタープライズ対応
業務システムを作る上で避けて通れない要件がしっかりサポートされています。
- 認証: Okta、Azure AD、Amazon Cognito対応
- アクセス制御: ACL、RBAC、ABAC対応
- セルフホスト: 自社インフラでの運用が可能
- 監査ログ: 利用状況の追跡
セキュリティ要件が厳しい企業案件でも安心して提案できるレベル。
マルチプラットフォーム対応
Next.js、Remix、React Native、Electronなど、複数のプラットフォームで利用可能。Webだけじゃなくモバイルやデスクトップアプリも同じ知識で作れるのは、時短になるという話です。
インストール方法
セットアップは非常にシンプル。以下のコマンド一発でプロジェクトが作成できます。
npm create refine-app@latest my-refine-app
対話式のウィザードが起動して、UIフレームワークやバックエンドの選択ができます。個人的にはMaterial UIとSupabaseの組み合わせがコスパ的に優秀だと思います。
cd my-refine-app
npm run dev
これだけで開発サーバーが立ち上がり、すぐに開発を始められます。
Refineのサンプルを動かす
Refineの核となる仕組みはdataProviderです。バックエンドが何であっても、getList・getOne・create・update・deleteOneといったメソッドを実装したオブジェクトを渡すだけで、useTableやuseCreate・useUpdateといったRefineのフックが同じ書き方でデータを扱えるようになります。ここでは実際のAPIの代わりに、配列を操作するだけのインメモリdataProviderを渡して、ToDoリストのCRUDをブラウザだけで動かしてみます。
dataProviderの要点だけを抜き出すと、次のようになります。getListが一覧を返し、create・updateがそれぞれ配列に追加・書き換えを行うだけのシンプルな実装です。
const dataProvider = {
getApiUrl: () => "",
getList: async () => ({ data: records, total: records.length }),
getOne: async ({ id }) => ({ data: records.find((r) => r.id === id) }),
create: async ({ variables }) => {
const record = { id: nextId++, done: false, ...variables };
records = [...records, record];
return { data: record };
},
update: async ({ id, variables }) => {
records = records.map((r) => (r.id === id ? { ...r, ...variables } : r));
return { data: records.find((r) => r.id === id) };
},
deleteOne: async ({ id }) => {
records = records.filter((r) => r.id !== id);
return { data: {} };
},
};
// resource名を指定するだけで、一覧取得・作成・更新がフックから呼べる
const { result } = useTable({ resource: "todos" });
const { mutate: createTodo } = useCreate();
実際に動かせるものが下です。入力欄にやることを書いて「追加」を押すとuseCreate経由でdataProvider.createが呼ばれ、チェックボックスをオンにするとuseUpdate経由でdataProvider.updateが呼ばれます。どちらも成功後に一覧クエリが自動で再取得されるので、useTable側で明示的な再読み込み処理を書く必要はありません。テレメトリ送信はoptions={{ disableTelemetry: true }}で止めているので、外部への通信は一切発生しません。
dataProvider.updateの中身をrecords.map(...)からrecords.filter((r) => r.id !== id)に変えてdeleteOneと繋ぎ直せば、そのまま削除ボタンにもできます。この後の「基本的な使い方」「実践的なユースケース」では、実際のUIライブラリと組み合わせたときの書き方を見ていきます。
基本的な使い方
Refineコンポーネントの設定
まずはエントリーポイントでRefineの設定を行います。
import { Refine } from "@refinedev/core";
import { ThemedLayoutV2 } from "@refinedev/mui";
import routerProvider from "@refinedev/react-router-v6";
import dataProvider from "@refinedev/simple-rest";
import { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Refine
dataProvider={dataProvider("https://api.example.com")}
routerProvider={routerProvider}
resources={[
{
name: "posts",
list: "/posts",
create: "/posts/create",
edit: "/posts/edit/:id",
show: "/posts/show/:id",
},
]}
>
<Routes>
<Route
element={<ThemedLayoutV2><Outlet /></ThemedLayoutV2>}
>
<Route path="/posts" element={<PostList />} />
<Route path="/posts/create" element={<PostCreate />} />
<Route path="/posts/edit/:id" element={<PostEdit />} />
<Route path="/posts/show/:id" element={<PostShow />} />
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
}
リスト画面の実装
データ一覧を表示する画面は、useDataGridフックを使えばすぐに作れます。
import { useDataGrid } from "@refinedev/mui";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import { List } from "@refinedev/mui";
export const PostList = () => {
const { dataGridProps } = useDataGrid();
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 90 },
{ field: "title", headerName: "タイトル", flex: 1 },
{ field: "status", headerName: "ステータス", width: 120 },
{ field: "createdAt", headerName: "作成日", width: 150 },
];
return (
<List>
<DataGrid {...dataGridProps} columns={columns} autoHeight />
</List>
);
};
たったこれだけで、ページネーション・ソート・フィルタリング機能付きのデータテーブルが完成。正直なところ、自前で実装していた頃のコード量を考えると笑えてきます。
フォーム画面の実装
作成・編集フォームも同様にシンプル。
import { Create } from "@refinedev/mui";
import { useForm } from "@refinedev/react-hook-form";
import { TextField, Box } from "@mui/material";
export const PostCreate = () => {
const {
saveButtonProps,
register,
formState: { errors },
} = useForm();
return (
<Create saveButtonProps={saveButtonProps}>
<Box component="form" sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<TextField
{...register("title", { required: "タイトルは必須です" })}
label="タイトル"
error={!!errors.title}
helperText={errors.title?.message as string}
/>
<TextField
{...register("content")}
label="内容"
multiline
rows={4}
/>
</Box>
</Create>
);
};
React Hook Formとの統合も標準でサポートされているので、バリデーションも楽に実装できます。
実践的なユースケース
社内管理システム
従業員管理、在庫管理、受発注管理など、社内で使う業務システムの構築に最適。認証・権限管理が標準で付いているので、「この画面は管理者だけ見れるようにして」みたいな要件もすぐに対応できます。
従業員一覧のような社内管理システムでは、部署や役職で絞り込みたい場面がよくあります。RefineのuseTableはfiltersとsetFiltersを返してくれるので、絞り込み条件を状態として持つ必要がありません。dataProvider.getListに渡ってくるfilters引数を見て、該当するレコードだけ返すようにするだけです。
const { result, filters, setFilters } = useTable({ resource: "employees" });
// フィルタをセットすると dataProvider.getList が自動で呼び直される
setFilters([{ field: "department", operator: "eq", value: "開発" }]);
// dataProvider 側では filters 引数を見て絞り込むだけでよい
function getList({ filters }) {
const dept = filters?.find((f) => f.field === "department")?.value;
const data = dept ? employees.filter((e) => e.department === dept) : employees;
return { data, total: data.length };
}
下のセレクトボックスで部署を切り替えると、setFiltersが呼ばれてuseTableの一覧が絞り込まれます。「すべての部署」に戻すとfiltersが空配列になり、全件表示に戻ります。
operator: "eq"の部分を"contains"に変え、セレクトボックスをテキスト入力に差し替えれば、そのまま名前の部分一致検索にも応用できます。
SaaSの管理画面
自社サービスの管理ダッシュボードを作るときにも威力を発揮。ユーザー管理、課金管理、データ分析画面など、SaaSに必要な機能を素早く実装できます。
SaaSの管理画面で頻出するのが、ユーザーごとの契約プランを編集する画面です。RefineのuseFormはaction: "edit"を指定すると、内部でdataProvider.getOneを呼んで初期値を読み込み、onFinishを呼ぶとdataProvider.updateで保存してくれます。編集後は詳細クエリも自動で再取得されるので、保存した内容がそのまま画面に反映されます。
const { query, onFinish, formLoading } = useForm({
resource: "users",
action: "edit",
id: 1,
redirect: false, // ルーターを使わないので遷移はさせない
});
const currentPlan = query?.data?.data?.plan; // getOne の結果が自動で入る
onFinish({ plan: "pro" }); // update を呼び、detailクエリも再取得される
下のセレクトボックスでプランを変えて「保存」を押すと、useForm経由でdataProvider.updateが実行され、上部の表示も更新後の値に切り替わります。
action: "edit"を"create"に変えてidを外せば、同じuseFormのコードのまま新規作成フォームとしても使い回せます。
クライアントワーク
受託開発で「管理画面も作ってほしい」と言われたとき、Refineを使えば工数を大幅に削減できます。その分、コア機能の開発に時間を使えるようになるので、クライアントにとっても開発者にとってもWin-Win。
プロトタイピング
新規事業のMVP開発など、スピードが求められる場面でも活躍。基本的なCRUD機能をサクッと作って、ビジネス検証に集中できます。
他のツールとの比較
正直なところ、管理画面構築ツールは他にもあります。
- React Admin: 老舗で情報も多いが、やや古い設計思想
- AdminBro (AdminJS): Node.js特化だがカスタマイズ性に制限
- Retool: ノーコードだが有料でベンダーロックイン
Refineは、これらの良いところ取りをしつつ、モダンなReact開発のベストプラクティスに沿った設計になっています。TypeScriptのサポートも手厚く、開発体験としてはかなり洗練されている印象。
まとめ
Refineは、管理画面・ダッシュボード開発における多くの課題を解決してくれるフレームワークです。
- ヘッドレス設計で好きなUIフレームワークを使える
- 15以上のバックエンドサービスと統合可能
- 認証・権限管理・監査ログなどエンタープライズ要件に対応
- TypeScriptで型安全な開発ができる
30代になると、同じことを繰り返すのが本当に嫌になってくるんですよね。Refineを使えば、面倒なボイラープレートから解放されて、本質的な価値を生む開発に集中できます。
次に管理画面を作る機会があったら、ぜひRefineを試してみてください。QOL上がること間違いなしです。