PocketBaseを試す
Nuxtと合わせて導入予定のPocketBaseを触ってみたいと思います。
ダウンロード
まず初めに、PocketBase公式サイトからファイルをダウンロードします。今回はApple Silicon MacなのでDownload v0.42.2 for macOS ARM64を選びます。

PocketBaseの実行と設定
次に、ダウンロードできたら展開して展開されたディレクトリに移動し以下のコマンドを実行します。macOSのセキュリティで最初は開けないので設定->プライバシーとセキュリティで許可した後に再度開きます。
% ./pocketbase serve
そうすると以下のように管理画面とAPIエンドポイントの情報が出てくるので管理画面にアクセスします。アクセス後、メールアドレスとパスワードの設定を求められるので設定します。
2026/11/14 20:42:26 Server started at http://127.0.0.1:8090
> Dashboard: http://127.0.0.1:8090/_/
> REST API: http://127.0.0.1:8090/api/
起動すると、以下のような画面になるので、左の+ New collectionを押します。

今回はtestという名前で、name(Text)フィールドを追加したコレクションを作成します。入力が完了したらCreateを押します。

次に、デフォルトではDBへのアクセスはSuperusers onlyとなっているので、とりあえず全員無条件でアクセスできるようにしたいと思います。
先ほどの画面の歯車マークを教えて、API rulesタブを開き、鍵マークを全部押すとこのようになります。Save changesを押して保存します。

なんと親切なことに右上のAPI previewを押すとFlutterでの実装例を教えてくれます。今回はこの通りに実装してみたいと思います。

Flutterでの実装
今回は、
% flutter create pocketbase_demo
でプロジェクトを作成し、DBから読み書きする簡単なアプリケーションを作ってみました。
デフォルト状態ではデータが入っていません。

追加したい名前にデータを入れ、下の追加ボタン(Icon(Icons.add))を押します。

その後、ダウンロードボタン(Icon(Icons.get_app))を押してデータを取得します。先ほど追加した内容を取得できました。

管理画面側でも増えていることを確認できます。

感想
Supabaseなどはかなり重いですが、PocketBaseはその名の通りPocketに入りそうなぐらい軽くて使いやすい(DLファイルが12.2MB)と感じました。Flutter向けの説明がかなり親切なのもポイントです。
欠点としては、公式のIntroductionにもあるとおり、開発中なのでv1.0.0までは後方互換性が保証されないことでしょうか。

余談ですが、オウンドメディアは気楽に投稿できて良い!
おまけ
Flutterコード載せておきます。クソコードなのは気にしないでください。
import 'package:flutter/material.dart';
import 'package:pocketbase/pocketbase.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List resultList = [];
late TextEditingController _controller;
@override
void initState(){
super.initState();
_controller = TextEditingController();
}
void getDataFromPocketBase() async {
final pb = PocketBase('http://127.0.0.1:8090');
final pbresultList = await pb
.collection('test')
.getList(page: 1, perPage: 50);
setState(() {
resultList = pbresultList.items;
});
}
void addDataToPocketBase() async {
final pb = PocketBase('http://127.0.0.1:8090');
final body = <String, dynamic>{"name": _controller.text};
await pb.collection('test').create(body: body, files: []);
_controller.text = "";
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: .center,
children: [
if (resultList.isEmpty)...{
Text('data is empty')
}else...{
for (RecordModel data in resultList) ...{
Text("${data.id}:${data.data['name']}"),
},
},
Container(width: 400,margin: EdgeInsets.all(30),child: TextField(
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: '追加したい名前',
),
),)
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
onPressed:getDataFromPocketBase,
tooltip: 'Increment',
child: const Icon(Icons.get_app),
),
FloatingActionButton(
onPressed: addDataToPocketBase,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
],
),
);
}
}