はじめに
リアルタイムに更新されるアプリを作ろうとすると、途端にやることが増えます。WebSocketサーバーを立て、クライアントとの通信プロトコルを決め、データベースの変更をどう検知して誰に配信するか設計し、サーバーとクライアントで二重に書いたバリデーションのズレに悩む——チャットや共同編集ツールを自前で作ったことがある人なら、身に覚えがあるのではないでしょうか。
Meteorは、この配線をプラットフォームごと引き受けてくれるフルスタックJavaScriptプラットフォームです。MongoDBへの書き込みが自動的に接続中の全クライアントへ配信され、UIが再レンダリングされます。データの流れを自分で組み立てる必要がなく、「MongoDBを更新すれば画面が変わる」という体験がフレームワークの標準機能として最初から用意されています。
Meteorとは
Meteorは2012年に登場した、Node.js上で動くフルスタックJavaScriptプラットフォームです。サーバー・クライアント間の通信にDDP(Distributed Data Protocol)という独自プロトコルを使い、MongoDBのoplog(操作ログ)を監視することで、データベースの変更をリアルタイムに全クライアントへ配信します。
長らくNode.jsの非同期処理をFiberというライブラリで同期的に書けるようにしていましたが、Meteor 3.0でFiberを全廃し、標準のasync/awaitに全面移行しました。現在はMeteor 3.5系がリリースされており、Node.js 22以降が必要です(3.5系ではNode.js 24対応も進められています)。ビルド周りもMeteor 3.3でBabelからSWCへ切り替わりビルド時間が約60%短縮、Meteor 3.4ではRspackの採用でさらに約4倍高速化・バンドルサイズが88%削減されるなど、直近のバージョンでも活発に改善が続いています。
主な特徴
- リアルタイム同期が標準機能 -
publish/subscribeでデータを購読すると、MongoDBの変更が自動的にクライアントへ届きます - Minimongoによる楽観的UI更新 - クライアント側にMongoDBのサブセットを保持する
Minimongoが動き、サーバーの応答を待たずにUIを即座に更新する「latency compensation」を実現します - UIフレームワークを選べる - React・Vue・Svelte・Solid・Blazeのいずれでもチュートリアルが用意されており、既存のフロントエンド資産を持ち込めます
- 認証・モバイル・ホスティングが一体型 -
accounts-passwordなどの認証パッケージ、Cordovaによるモバイルビルド、公式ホスティングのGalaxyまでが同じCLIから扱えます - パッケージレジストリAtmosphere - npmに加えて、Meteor専用パッケージを配布するAtmosphereというエコシステムを持ちます
インストール
MeteorのCLIはnpm経由でインストールできます(Node.js 22以降が必要です)。
npx meteor
または、グローバルインストールする場合は以下のコマンドを使います。
npm install -g meteor
インストールできたら、新しいプロジェクトを作成して起動します。meteor runはローカルのMongoDBとNode.jsサーバーを自動で立ち上げ、ファイル変更を検知してホットリロードします。
meteor create my-app --react
cd my-app
meteor run
http://localhost:3000にアクセスすると、アプリが起動していることを確認できます。
基本的な使い方
Meteorのプロジェクトでは、サーバー用コードをserver/、クライアント用コードをclient/、両方から参照するコードをimports/に置くのが基本構成です。まずはサーバー側でMongoDBコレクションを定義し、データを公開してみましょう。
// imports/api/tasks.js
import { Mongo } from 'meteor/mongo';
export const TasksCollection = new Mongo.Collection('tasks');
// server/main.js
import { Meteor } from 'meteor/meteor';
import { TasksCollection } from '/imports/api/tasks';
Meteor.startup(async () => {
// 初回起動時にサンプルデータを投入
if ((await TasksCollection.find().countAsync()) === 0) {
await TasksCollection.insertAsync({ text: '最初のタスク' });
}
});
// タスク一覧を全クライアントに公開する
Meteor.publish('tasks', function () {
return TasksCollection.find();
});
クライアント側では、公開されたデータをsubscribeで購読するだけで、Reactの場合はuseTrackerを通じてリアクティブにデータを受け取れます。サーバーでinsertAsyncが呼ばれた瞬間、この一覧は自動で再レンダリングされます。
// imports/ui/TaskList.jsx
import React from 'react';
import { useTracker, useSubscribe } from 'meteor/react-meteor-data';
import { TasksCollection } from '/imports/api/tasks';
export const TaskList = () => {
const isLoading = useSubscribe('tasks');
const tasks = useTracker(() => TasksCollection.find().fetch());
if (isLoading()) return <p>読み込み中...</p>;
return (
<ul>
{tasks.map((task) => (
<li key={task._id}>{task.text}</li>
))}
</ul>
);
};
実践的なユースケース
Meteorは1つの書き方だけでなく、データの取り扱い方によっていくつかのパターンを使い分けます。ここでは代表的な4つの使い方を見ていきます。
リアルタイム同期(publish/subscribe)
複数人が同時に見る画面で「誰かが更新したら、他の人の画面にも即座に反映したい」というケースは、Meteor.publishとsubscribeの組み合わせで解決します。サーバーが返すカーソルの中身が変わると、MongoDBのoplogをトリガーにMeteorが差分だけを購読中の全クライアントへ配信します。
// server/main.js
Meteor.publish('tasks.active', function () {
// 完了していないタスクだけを公開する
return TasksCollection.find({ done: false }, { sort: { createdAt: -1 } });
});
// クライアント側(React)
const isLoading = useSubscribe('tasks.active');
const tasks = useTracker(() => TasksCollection.find().fetch());
findの条件を絞れば、そのまま配信対象も絞り込まれます。チャットのメッセージ一覧や進捗ボードのように、複数クライアント間で常に同じ状態を保ちたい画面に向いた書き方です。
メソッドによるサーバーロジック呼び出し(RPC)
購読だけでなく、「ボタンを押したらサーバー側で処理を実行したい」という場面ではMeteor.methodsを使います。クライアントからMeteor.callAsyncで呼び出すと、サーバーで実際の処理が終わる前にクライアント側でも同じロジックが仮実行され、体感的な待ち時間がなくなる「latency compensation」が働きます。
// imports/api/tasks.js
import { Meteor } from 'meteor/meteor';
Meteor.methods({
async 'tasks.insert'(text) {
return TasksCollection.insertAsync({ text, done: false, createdAt: new Date() });
},
async 'tasks.setDone'(taskId, done) {
return TasksCollection.updateAsync(taskId, { $set: { done } });
},
});
// クライアント側
await Meteor.callAsync('tasks.insert', '新しいタスク');
サーバーの応答を待たずに画面上のタスクが増えたように見えるのは、クライアント側のMinimongoが同じtasks.insertロジックを先取り実行し、サーバーの結果が届いた時点で正しい値に差し替えているためです。
認証(accounts-password)
ユーザー登録・ログインの仕組みも、パッケージを追加するだけで組み込めます。accounts-passwordを追加すると、パスワードのハッシュ化やセッション管理をMeteorが引き受けてくれます。
meteor add accounts-password
// クライアント側
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';
Accounts.createUser({ email: 'user@example.com', password: 'secret123' });
Meteor.loginWithPassword('user@example.com', 'secret123', (err) => {
if (err) console.error(err);
});
Google・GitHubなどのOAuthを使いたい場合も、accounts-googleのようなパッケージを追加してAPIキーを設定するだけで済み、認証フローを自前で実装する必要はありません。
マルチプラットフォーム展開
Meteorは同じコードベースをWeb・iOS・Android向けにビルドできます。モバイルは公式にCordovaと統合されており、以下のコマンドでプラットフォームを追加してビルドします。
meteor add-platform android
meteor run android-device
デスクトップアプリ化はmeteor-desktopのようなElectron統合の仕組みを使うのが一般的で、こちらはコミュニティ主導のプロジェクトです。公式ホスティングのGalaxyにデプロイする場合は、以下の1コマンドで完了します。
meteor deploy my-app.meteorapp.com
サーバー・モバイル・デスクトップ・デプロイまでを同じCLIで完結させられる点が、他のフレームワークと比べたMeteorの大きな違いです。
まとめ
Meteorは、DDPによるリアルタイム通信とMinimongoによる楽観的UI更新を標準機能として持つ、数少ないフルスタックプラットフォームです。Meteor 3.0でのFiber廃止・async/await化、SWCやRspackの採用による高速化など、直近も継続的に進化を続けています。publish/subscribeでデータを流し、Meteor.methodsでロジックを呼び、accounts-passwordで認証を足す——この3つの組み合わせを覚えるだけで、リアルタイム性のあるアプリの土台がすぐに動き始めます。まずはmeteor createでプロジェクトを作り、tasksコレクションを公開するところから試してみてください。