私は長年にわたり業務でFlutterアプリを開発してきましたが、バックエンドの状況がFirebaseからSupabase、そしてカスタムNode.js APIへと進化していくのを目の当たりにしてきました。
この進化の中で、それぞれのソリューションにはトレードオフがありました。Firebaseはベンダーロックインの問題があり、Supabaseはカスタムロジックが必要になるまでは素晴らしいものでした。そして、別途Node.jsバックエンドを維持する場合は、TypeScriptとDartの間でコンテキストスイッチが必要になるという課題がありました。
正直なところ、新しいバックエンドソリューションを積極的に探していたわけではありません。しかしServerpodはフィード、Flutterニュースレター、他の開発者との会話など、至る所で目や耳にする機会も増え、その注目度が本物であることを身をもって感じていました。
そこで、これらの注目度に比例するような実態があるかどうかを確かめてみたく、週末を使って試してみることにしました。 今回は、私が試用の中で発見したいくつかのポイントについてご紹介します。
セットアップ: 最初の驚き
通常のセットアップの摩擦を予想していました。Dockerの設定、環境変数、データベース接続文字列、認証ミドルウェア。確かに、ClaudeやCursorに定型文のヘルプを頼むこともできますが、AIエージェントがスピードアップしてくれても、バックエンドスタックを立ち上げるには、実際のビジネスロジックを書く前に1時間のセットアップが必要です。
代わりに:
$ serverpod create weekend_experiment
Creating project...
✓ Server setup complete (packages/weekend_experiment_server)
✓ Client library generated (packages/weekend_experiment_client)
✓ Flutter app created (weekend_experiment_flutter)
✓ Database migrations ready
✓ Docker compose configured
プロジェクト構造は、Flutterの開発者にとってすぐに馴染みのあるものでした:
weekend_experiment/
├── weekend_experiment_server/ # バックエンドコード
├── weekend_experiment_client/ # 自動生成されたAPIクライアント
├── weekend_experiment_flutter/ # Flutterアプリ
└── docker-compose.yaml # PostgreSQL + Redis準備完了
サーバーディレクトリでdocker compose upを実行しました。PostgreSQLとRedisが起動しました。次にdart run bin/main.dart --apply-migrationsでデータベースを初期化しました。
良い点: 実際に感銘を受けたこと
1. あなたと戦わないORM
私は良いデータベースツールがどのように感じられるかを知るのに十分なSQLを書いてきました。また、恐ろしいクエリを生成し、簡単なことを難しくするORMに苦しんできました。
ServerpodのORMをテストするためにモデルファイルを作成しました:
# user.spy.yaml
class: User
table: user
fields:
name: String
email: String
age: int?
premium: bool
createdAt: DateTime
indexes:
- fields: email
unique: true
serverpod generateを実行しました。フレームワークは以下を生成しました:
- すべてのフィールドを持つDartクラス
- JSONシリアライズ/デシリアライズ
- データベースアクセスメソッド
- クライアント側モデル
- マイグレーションファイル
ここで興味深くなりました。クエリAPIは自然に感じられました:
// 25歳以上のすべてのプレミアムユーザーを作成日順で検索
final users = await User.db.find(
session,
where: (t) => t.premium.equals(true) & t.age.greaterThan(25),
orderBy: (t) => t.createdAt.desc(),
limit: 20,
);
// リレーションを使った複雑な結合
final postsWithAuthors = await Post.db.find(
session,
where: (t) => t.publishedAt.isNotNull(),
include: (t) => [t.author, t.comments],
);
型安全。文字列のカラム名なし。SQLインジェクション不可能。IntelliSenseが利用可能なフィールドを表示。
意図的に壊れたクエリでテストしました:
where: (t) => t.nonExistentField.equals(true) // コンパイルエラー!
Dartアナライザーがすぐにそれを検出しました。ランタイムのサプライズなし。タイプミスによる本番環境の問題のデバッグなし。
2. 実際に機能するリアルタイム
ほとんどのリアルタイム実装には、WebSocketサーバーのセットアップ、接続/切断の処理、サブスクリプションの管理、再接続ロジックの処理、状態の同期が含まれます。
Serverpodのストリーミング機能を簡単なカウンターでテストしてみました。
サーバーエンドポイント:
class CounterEndpoint extends Endpoint {
Stream watchCounter(Session session) async* {
for (int i = 0; i < 100; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
}
クライアントの使用:
client.counter.watchCounter().listen((count) {
print('Counter: $count');
});
以上です。ServerpodはWebSocket接続管理、ネットワーク損失時の自動再接続、クライアントが遅い場合のバックプレッシャー、クリーンなストリームの廃棄を処理します。
データベース駆動のリアルタイム更新の場合:
class ChatEndpoint extends Endpoint {
Stream watchRoom(Session session, int roomId) async* {
await for (var message in Message.db.watch(
session,
where: (t) => t.roomId.equals(roomId),
)) {
yield message;
}
}
}
そのチャットルームのメッセージへのデータベース変更は、接続されているすべてのクライアントに自動的にストリーミングされます。手動pub/subセットアップなし。Redisチャンネルなし。メッセージキューなし。
3. 理にかなったコード生成
Serverpodの生成は単純明快です。YAMLで定義されたモデル、サーバー構造から生成されたコード。出力は読みやすいDartです—マジックなし、複雑さを隠す抽象化なし。何か問題があるとき、実際に何が起こっているかを見ることができます。
$ serverpod generate
Analyzing server...
✓ Found 5 models
✓ Found 3 endpoints
Generating...
✓ Client library (packages/weekend_experiment_client/)
✓ Protocol models
✓ Database migrations
Time: ~5 seconds
4. 涙なしの認証
認証のセットアップは年々簡単になってきました—多くのライブラリがOAuthフローを処理します。しかし、それらを統合するには、設定、コールバックのテスト、環境全体で機能することの確認が必要です。
Serverpodには、Google、Apple、メール認証をサポートするserverpod_authモジュールが含まれています。
サーバーセットアップ:
import 'package:serverpod_auth/server.dart';
// 認証はserverpod_authパッケージを通じて設定されます
クライアントセットアップ:
import 'package:serverpod_auth/client.dart';
// Googleでサインイン
final result = await client.auth.signInWithGoogle();
if (result.success) {
print('Welcome ${result.userInfo.userName}');
}
セッションには自動的に認証されたユーザーが含まれます:
class ProfileEndpoint extends Endpoint {
Future getMyProfile(Session session) async {
final userId = session.userId;
if (userId == null) throw Exception('Not authenticated');
return await Profile.db.findById(session, userId);
}
}
JWT解析なし。ミドルウェアチェーンなし。認証ヘッダー管理なし。
(プロのヒント: iOSアプリにGoogle Sign-Inを追加する場合、Apple Sign-Inも忘れないでください。さもないとAppleはアプリを喜んで拒否します。)
5. 開発者体験
これは微妙ですが重要です: すべてがDartです。
- サーバーコード? Dart。
- クライアントコード? Dart。
- データベースモデル? Dart (YAMLを介してですが、Dartのように感じます)。
- テスト? Dart。
- ツール? Dart CLI。
クライアントとサーバーの両方をDartで書くことで、言語のコンテキストスイッチが排除されます。ツール、パターン、規約はスタック全体で一貫しています。エコシステムを切り替える認知的オーバーヘッドなしに、フロントエンドとバックエンドの作業の間を移動できます。
悪い点: 不足しているところ
ここで物事は完璧ではありませんでした。
1. 小さなパッケージエコシステム
サードパーティのパッケージエコシステムは、ExpressやDjangoと比較して限られています。いくつかのコミュニティパッケージ(Stripe統合など)は見つかりますが、同じ幅広い選択肢はありません。特定のCMS統合が必要ですか? Elasticsearchコネクタ? Twilioラッパー? おそらく自分で構築するか、既存のDartパッケージを適応させることになります。
AIコーディングアシスタントは一般的なDartパターンで役立ちますが、Serverpod固有の知識は限られています。標準的なCRUD操作と基本的なサーバーパターンには役立ちますが、主流のフレームワークよりもドキュメントとソースコードを読むことが多くなります。
プロジェクトが特殊なサービスとの統合に大きく依存している場合、または広範なサードパーティツールが必要な場合は、追加の実装時間を考慮してください。
2. 複雑な機能の学習曲線
基本はスムーズです。エンドポイントの作成、モデルの定義、シンプルなクエリ - すべて直感的です。
高度な機能は? ドキュメントは基礎となる概念を理解していることを前提としています。
複雑な認可パターン、高度なトランザクション処理、複雑なデータベースリレーションシップには、GitHubイシューとAIからのソリューションを組み合わせる必要があります。
3. マイグレーションの複雑さ
データベースマイグレーションは、シンプルな変更—nullableフィールドの追加、テーブルの作成—ではうまく機能します。複雑な変更には手動介入が必要です:
- データ変換
- データを保持しながら列の名前を変更
- テーブルの分割
- 複雑な制約の変更
マイグレーションシステムはこれらのシナリオを自動的に処理しません。生成されたSQLマイグレーションを手動で編集できますが、これにより型安全なORMの利点の一部が減少し、慎重なテストが必要になります。
4. 本番環境ドキュメントのギャップ
ローカルで始めるには? 完璧です。
本番環境にデプロイするには? ドキュメントはDockerの基本とヘルスチェックエンドポイントをカバーしていますが、本番環境のDevOpsの詳細は自分で理解する必要があります:
- Kubernetes固有の設定(liveness/readinessプローブ、HPA)
- ゼロダウンタイムデプロイのためのグレースフルシャットダウン
- データベースのバックアップと災害復旧戦略
- ログの集約と集中監視
- 本番環境グレードのセキュリティ強化
コミュニティのserverpod_vpsパッケージが役立ち、GitHubディスカッションがいくつかのギャップを埋めますが、包括的な本番運用ガイドは価値があるでしょう。
5. パフォーマンスデータ
ユーザーの証言では、Serverpodが本番環境で1日あたり100,000以上のリクエストを処理していることに言及しており、他のフレームワークと比較するコミュニティベンチマークがあります。しかし、包括的なパフォーマンスドキュメントは乏しいです。
- さまざまな負荷下でのレイテンシ分布
- 水平スケーリングパターン
- 負荷下でのメモリフットプリント
- データベース接続プールのベストプラクティス
- キャッシング戦略ガイドライン
これにより、大規模アプリケーションのキャパシティプランニングがより不確実になります。
驚き: 予想していなかったこと
1. ファイルアップロードシステムが実際に良い
ほとんどのファイルアップロード実装は苦痛です。Serverpodはここで私を驚かせました。
// サーバーエンドポイント
class FileEndpoint extends Endpoint {
Future getUploadDescription(Session session, String fileName) async {
return await session.storage.createDirectFileUploadDescription(
storageId: 'public',
path: fileName,
);
}
}
// クライアントはクラウドストレージに直接アップロード
final uploadDescription = await client.file.getUploadDescription('photo.jpg');
await uploader.upload(uploadDescription, fileBytes);
これにより、直接クラウドストレージアップロード用の事前署名されたURLが生成されます。サーバーを通過するファイルデータはありません。S3とGoogle Cloud Storageをサポートしています。
若いフレームワークでこのレベルの洗練を期待していませんでした。
2. キャッシングレイヤーが組み込まれている
Redis統合はボルトオンではなく、コアAPIの一部です:
// ユーザープロファイルをキャッシュ
await session.caches.redis.put(
'profile:$userId',
profile,
lifetime: Duration(minutes: 5),
);
// キャッシュから取得
final cached = await session.caches.redis.get('profile:$userId');
自動シリアライズを伴う型安全なキャッシング。キャッシュはセッションスコープで、手動の依存性注入なしにどこでも利用可能です。
3. 非同期バックグラウンドジョブのFuture Calls
ユーザー登録後にレスポンスをブロックせずにメールを送信する必要がありました:
class UserEndpoint extends Endpoint {
Future register(Session session, User user) async {
final created = await User.db.insertRow(session, user);
// Fire and forget - バックグラウンドで実行
await session.messages.sendFutureCall(
'email',
'sendWelcome',
{'userId': created.id},
delay: Duration(seconds: 10),
);
return created; // レスポンスはすぐに送信
}
}
これらの「future calls」はPostgreSQLに永続化され、サーバーの再起動を生き延び、正確に1回の実行セマンティクスを提供します。AWS SQS不要、RabbitMQ不要。
これにより、通常は別のメッセージキューインフラストラクチャを必要とするものが置き換えられました。
評決: もう一度使うか?
週末の探索の後、これが私の正直な評価です:
Serverpodを使用すべき場合:
- チーム全体がDartで作業するFlutter中心のアプリケーションを構築する場合
- フルスタック全体での型安全性が優先事項である場合
- リアルタイム機能が製品のコアである場合(チャット、コラボレーション、ライブ更新)
- エコシステムの成熟度よりも開発速度が重要な場合
- チームが単一言語エコシステムに留まることを好む場合
代替案を検討すべき場合:
- サードパーティ統合が広範で特殊な場合
- チームが複数の言語とスタックにまたがる場合(Python MLサービス、Goマイクロサービスなど)
- 本番環境のデプロイに包括的な運用ドキュメントが必要な場合
- レガシーシステムまたは独自APIとの統合が重要な場合
- プロジェクトが大規模な規模で実証されたスケーラビリティパターンを必要とする場合
結論
Serverpodの素晴らしい評判は妥当であると感じました。 クライアントからデータベースまでの型安全性を維持しながら、重要な定型文を排除する最新のフルスタックDartフレームワークでした。コード生成はクリーンで、ORMは実用的で、リアルタイム機能はWebSocketの複雑さを管理せずに機能します。
エコシステムはまだ発展途上であり、ドキュメントにはギャップがあるため、多くのエンジニアにとっては確立されたフレームワークよりも多くの統合コードを書くことになる可能性があります。しかし、リアルタイムアプリケーションを構築するFlutterチームであれば、最初の学習曲線を超えると高い生産性と優れた開発者体験を実感することができるはずです。
私はチームにそれを勧めますか? はい、エコシステムの成熟度と統合の制限について明確な期待を持って。
Node.js + Expressよりもそれを選びますか? チームがFlutterを知っている場合? 絶対に。そうでない場合? おそらくそうではありません。
Author

Muhammed Ayimen Abdul Latheef
Lead Engineer


