Convex で実現できること早見:チャット・コラボ・SaaS・ゲームの主要ユースケース総覧
Convexは、リアルタイムデータベースを中心としたフルスタックTypeScriptバックエンドプラットフォームです。WebSocketの複雑な実装を気にせず、チャット、コラボレーションツール、SaaSプロダクト、マルチプレイヤーゲームなど、リアルタイム性が求められるアプリケーションを簡単に構築できます。
本記事では、Convexで実現できる主要なユースケースを体系的に整理し、それぞれの実装イメージと活用ポイントを解説いたします。
Convexユースケース早見表
以下の表は、Convexで実装できる主要なユースケースを一覧化したものです。それぞれの用途に応じた実装の難易度や主要機能を確認できます。
| # | ユースケース | 主な活用機能 | リアルタイム性 | 実装難易度 | 代表的な実装例 |
|---|---|---|---|---|---|
| 1 | チャットアプリ | リアクティブクエリ、ミューテーション | 必須 | 低 | メッセージング、グループチャット |
| 2 | コラボレーションツール | リアルタイム同期、トランザクション | 必須 | 中 | 共同編集ドキュメント、タスク管理 |
| 3 | SaaSプロダクト | 認証、ストレージ、スケジューリング | 任意 | 中 | CRM、プロジェクト管理、顧客管理 |
| 4 | ゲーム(マルチプレイヤー) | リアルタイム同期、Actions | 必須 | 高 | リアルタイム対戦、協力プレイ |
| 5 | ライブダッシュボード | リアクティブクエリ、検索機能 | 推奨 | 低 | アナリティクス、監視画面 |
| 6 | 通知システム | Cron、Actions、リアルタイム配信 | 推奨 | 中 | プッシュ通知、アラート配信 |
| 7 | AIアプリケーション | ベクトル検索、Actions(外部API連携) | 任意 | 中〜高 | RAG実装、チャットボット |
背景
モダンアプリケーション開発の課題
近年のWebアプリケーション開発では、ユーザーがリアルタイムな体験を期待するようになりました。チャット機能、複数人での共同編集、リアルタイムな通知など、データの即時同期が求められるシーンが増えています。
しかし、従来のバックエンド開発では以下のような課題がありました。
mermaidflowchart TD
dev["開発者"] -->|実装| ws["WebSocket管理"]
dev -->|実装| db["データベース設計"]
dev -->|実装| auth["認証・認可"]
dev -->|実装| scale["スケーリング対応"]
ws -->|課題| complex1["接続管理の複雑さ"]
db -->|課題| complex2["マイグレーション管理"]
auth -->|課題| complex3["セキュリティ実装"]
scale -->|課題| complex4["インフラ運用コスト"]
complex1 --> burden["開発負担<br/>増大"]
complex2 --> burden
complex3 --> burden
complex4 --> burden
従来の開発では、リアルタイム通信のためのWebSocket実装、データベースのマイグレーション管理、認証システムの構築、そしてスケーリング対応など、本来のビジネスロジック以外に多くの時間を費やす必要がありました。
Convexが提供する解決策
Convexは、これらの課題を統合的に解決するバックエンドプラットフォームとして登場しました。TypeScriptを中心とした開発体験により、フロントエンドエンジニアでも扱いやすい設計になっています。
Convexの特徴は、データベースの変更をすべての接続中のクライアントに自動でリアルタイムに同期する点です。WebSocketを自分で管理する必要はなく、宣言的なAPIを通じて利用できます。
課題
リアルタイムアプリケーション開発の技術的障壁
リアルタイム性を持つアプリケーションを開発する際、多くの開発者が直面する課題があります。
WebSocket実装の複雑さ
リアルタイム通信を実現するには、通常WebSocketやServer-Sent Events(SSE)などの技術を使います。しかし、これらを直接扱うと以下のような問題が発生します。
- 接続の確立と切断の管理
- 再接続ロジックの実装
- メッセージの順序保証
- エラーハンドリング
- クライアント側の状態管理
データ整合性の確保
複数のユーザーが同時にデータを更新する環境では、データの整合性を保つことが重要です。トランザクション処理や競合解決のロジックを適切に実装しないと、データの不整合が発生してしまいます。
スケーラビリティの問題
ユーザー数が増えるにつれて、以下のような課題が顕在化します。
mermaidflowchart LR
users["ユーザー増加"] --> conn["接続数増大"]
conn --> server_load["サーバー負荷"]
server_load --> scale_out["スケールアウト<br/>必要"]
scale_out --> manage["管理コスト<br/>増大"]
scale_out --> db_sync["DB同期問題"]
scale_out --> session["セッション管理"]
db_sync --> complexity["運用複雑化"]
session --> complexity
manage --> complexity
開発速度とメンテナンス性のトレードオフ
自前でリアルタイム機能を実装すると、初期開発に時間がかかるだけでなく、メンテナンスコストも増大します。バグ修正、セキュリティアップデート、パフォーマンス改善など、継続的な対応が必要になります。
一方で、既存のBaaS(Backend as a Service)を使う場合でも、TypeScriptの型安全性が十分でなかったり、カスタマイズ性に制限があったりする課題がありました。
解決策
Convexによる包括的なバックエンドソリューション
Convexは、前述の課題を以下のアプローチで解決します。
リアクティブデータベース
Convexの中核となる機能は、リアクティブなデータベースです。クエリをサブスクライブするだけで、該当データが変化した際に自動で最新結果がクライアントにプッシュされます。
以下の図は、Convexのデータフローを示しています。
mermaidflowchart TB
client1["クライアント A"] -->|クエリ購読| convex["Convex<br/>プラットフォーム"]
client2["クライアント B"] -->|クエリ購読| convex
client3["クライアント C"] -->|クエリ購読| convex
convex -->|自動同期| db[("リアクティブ<br/>データベース")]
client2 -->|ミューテーション<br/>(データ更新)| convex
db -->|変更検知| convex
convex -->|リアルタイム<br/>プッシュ| client1
convex -->|リアルタイム<br/>プッシュ| client2
convex -->|リアルタイム<br/>プッシュ| client3
図で理解できる要点:
- クライアントはクエリを購読するだけでリアルタイム更新を受け取れる
- 1つのクライアントがデータを更新すると、すべての購読クライアントに自動配信される
- WebSocketの管理はConvexが内部で自動処理する
TypeScriptファーストな開発体験
Convexでは、データベーススキーマからバックエンド関数、クライアント呼び出しまで、すべてTypeScriptで記述します。これにより、型安全性を保ちながら開発を進められます。
ACIDトランザクション
Convexは、すべての操作でACID特性を満たすトランザクションを提供します。複数のデータ更新が必要な場合でも、データの整合性が自動的に保たれるため、開発者は複雑な競合制御を実装する必要がありません。
サーバーレスアーキテクチャ
Convexはフルマネージドなサーバーレス環境を提供します。インフラの管理、スケーリング、監視が自動化されており、開発者はビジネスロジックに集中できます。
具体例
ここからは、Convexを活用した具体的なユースケースを詳しく見ていきましょう。
チャットアプリケーション
実装の概要
チャットアプリは、Convexが最も得意とするユースケースの1つです。メッセージの送受信、既読管理、オンライン状態の表示など、リアルタイム性が求められる機能を簡単に実装できます。
データスキーマの定義
まず、Convexでチャットのデータスキーマを定義します。
typescript// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
// メッセージテーブル
messages: defineTable({
text: v.string(),
userId: v.id("users"),
channelId: v.id("channels"),
createdAt: v.number(),
})
.index("by_channel", ["channelId", "createdAt"])
.index("by_user", ["userId"]),
// チャンネルテーブル
channels: defineTable({
name: v.string(),
description: v.optional(v.string()),
createdAt: v.number(),
}),
});
このスキーマ定義により、TypeScriptの型情報が自動生成され、型安全にデータ操作ができます。
メッセージ送信のミューテーション
次に、メッセージを送信するためのミューテーション関数を実装します。
typescript// convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
// メッセージ送信のミューテーション
export const send = mutation({
// 引数の型定義
args: {
text: v.string(),
channelId: v.id("channels"),
},
// 実装ロジック
handler: async (ctx, args) => {
// 認証済みユーザーIDを取得
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthorized");
}
// メッセージをデータベースに保存
const messageId = await ctx.db.insert("messages", {
text: args.text,
userId: identity.subject,
channelId: args.channelId,
createdAt: Date.now(),
});
return messageId;
},
});
このミューテーションが実行されると、Convexは自動的に関連するクエリを購読しているすべてのクライアントに変更を通知します。
メッセージ取得のクエリ
メッセージを取得するクエリ関数を定義します。
typescript// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
// 特定チャンネルのメッセージを取得
export const list = query({
args: {
channelId: v.id("channels"),
},
handler: async (ctx, args) => {
// インデックスを使って効率的に取得
const messages = await ctx.db
.query("messages")
.withIndex("by_channel", (q) =>
q.eq("channelId", args.channelId)
)
.order("desc") // 新しい順
.take(100); // 最新100件
return messages;
},
});
フロントエンドでの利用
React(Next.js)でこれらの関数を利用する例です。
typescript// app/chat/[channelId]/page.tsx
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
import { useState } from "react";
上記のインポート文では、Convexのクライアントフックと、自動生成されたAPI型定義をインポートしています。
typescript// チャットコンポーネントの実装
export default function ChatPage({ params }: { params: { channelId: string } }) {
const [newMessage, setNewMessage] = useState("");
// メッセージをリアルタイムで購読
const messages = useQuery(api.messages.list, {
channelId: params.channelId as any,
});
// メッセージ送信のミューテーション
const sendMessage = useMutation(api.messages.send);
// 送信ハンドラー
const handleSend = async () => {
if (!newMessage.trim()) return;
await sendMessage({
text: newMessage,
channelId: params.channelId as any,
});
setNewMessage(""); // 入力欄をクリア
};
このコードでは、useQueryフックでメッセージをリアルタイム購読し、useMutationフックで送信機能を実装しています。
typescript // UIレンダリング
return (
<div>
<div className="messages">
{messages?.map((msg) => (
<div key={msg._id}>
<p>{msg.text}</p>
<small>{new Date(msg.createdAt).toLocaleString()}</small>
</div>
))}
</div>
<div className="input-area">
<input
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="メッセージを入力..."
/>
<button onClick={handleSend}>送信</button>
</div>
</div>
);
}
実装のポイント:
useQueryでクエリを購読すると、データが更新されるたびに自動的に再レンダリングされます- WebSocketの接続管理や再接続ロジックは不要です
- TypeScriptの型チェックにより、誤った引数や存在しないフィールドへのアクセスをコンパイル時に検出できます
コラボレーションツール
リアルタイム共同編集の実装
複数人で同じドキュメントを同時編集できる機能は、Convexのリアルタイム同期機能を活用することで実現できます。
以下の図は、共同編集のデータフローを示しています。
mermaidsequenceDiagram
participant UserA as ユーザー A
participant UserB as ユーザー B
participant Convex as Convex Platform
participant DB as Database
UserA->>Convex: ドキュメント購読
UserB->>Convex: ドキュメント購読
Convex->>DB: クエリ実行
DB-->>Convex: 現在のデータ
Convex-->>UserA: 初期データ配信
Convex-->>UserB: 初期データ配信
UserA->>Convex: テキスト編集(ミューテーション)
Convex->>DB: データ更新
DB-->>Convex: 更新完了
Convex-->>UserA: 更新確認
Convex-->>UserB: リアルタイム更新
UserB->>Convex: テキスト編集(ミューテーション)
Convex->>DB: データ更新
DB-->>Convex: 更新完了
Convex-->>UserB: 更新確認
Convex-->>UserA: リアルタイム更新
図で理解できる要点:
- 両ユーザーが同じドキュメントを購読している
- 一方が編集すると、もう一方にも即座に反映される
- トランザクション管理はConvexが自動処理する
プレゼンス機能の実装
誰が現在編集中かを表示するプレゼンス機能も実装できます。
typescript// convex/presence.ts
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
// ユーザーのオンライン状態を記録
export const updatePresence = mutation({
args: {
documentId: v.id("documents"),
cursorPosition: v.optional(v.number()),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return;
// プレゼンス情報を更新(5分でタイムアウト)
await ctx.db.insert("presence", {
userId: identity.subject,
documentId: args.documentId,
cursorPosition: args.cursorPosition,
lastActive: Date.now(),
});
},
});
この関数により、各ユーザーの編集位置や最終アクティブ時刻を追跡できます。
typescript// アクティブユーザーを取得するクエリ
export const getActiveUsers = query({
args: {
documentId: v.id("documents"),
},
handler: async (ctx, args) => {
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
// 5分以内にアクティブだったユーザーを取得
const activePresence = await ctx.db
.query("presence")
.filter((q) =>
q.and(
q.eq(q.field("documentId"), args.documentId),
q.gte(q.field("lastActive"), fiveMinutesAgo)
)
)
.collect();
return activePresence;
},
});
SaaSプロダクトの構築
認証とマルチテナント
Convexは80以上のOAuth統合に対応しており、Clerkなどの認証サービスとシームレスに連携できます。
typescript// convex/organizations.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
// 組織(テナント)のデータ取得
export const getOrganization = query({
args: {
orgId: v.id("organizations"),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Unauthorized");
}
// ユーザーが組織のメンバーか確認
const membership = await ctx.db
.query("memberships")
.withIndex("by_user_org", (q) =>
q.eq("userId", identity.subject).eq("orgId", args.orgId)
)
.first();
if (!membership) {
throw new Error("Access denied");
}
return await ctx.db.get(args.orgId);
},
});
このように、組織ごとのデータアクセス制御を実装できます。
Cronジョブとスケジューリング
定期的なタスク実行もConvexで簡単に実装できます。
typescript// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// 毎日午前2時にレポート生成
crons.daily(
"generate daily reports",
{ hourUTC: 2, minuteUTC: 0 },
internal.reports.generateDaily
);
// 5分ごとにデータ同期
crons.interval(
"sync external data",
{ minutes: 5 },
internal.sync.syncExternalData
);
export default crons;
Cronジョブの設定をコードで管理でき、デプロイと同時に自動的に適用されます。
ファイルストレージ機能
Convexには組み込みのファイルストレージ機能があります。
typescript// convex/files.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
// ファイルアップロード用のURLを生成
export const generateUploadUrl = mutation({
handler: async (ctx) => {
return await ctx.storage.generateUploadUrl();
},
});
フロントエンドからこのURLを使ってファイルをアップロードします。
typescript// ファイルメタデータを保存
export const saveFileMetadata = mutation({
args: {
storageId: v.string(),
filename: v.string(),
mimeType: v.string(),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
return await ctx.db.insert("files", {
storageId: args.storageId,
filename: args.filename,
mimeType: args.mimeType,
uploadedBy: identity.subject,
uploadedAt: Date.now(),
});
},
});
ゲーム(マルチプレイヤー)
リアルタイム対戦ゲームの実装
Convexを使えば、簡単なマルチプレイヤーゲームを構築できます。
typescript// convex/game.ts
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
// ゲームセッションの作成
export const createGameSession = mutation({
args: {
maxPlayers: v.number(),
},
handler: async (ctx, args) => {
const sessionId = await ctx.db.insert("gameSessions", {
maxPlayers: args.maxPlayers,
players: [],
status: "waiting",
createdAt: Date.now(),
});
return sessionId;
},
});
ゲームセッションを作成する関数では、最大プレイヤー数を設定し、初期状態を定義します。
typescript// プレイヤーのアクション処理
export const playerAction = mutation({
args: {
sessionId: v.id("gameSessions"),
action: v.object({
type: v.string(),
x: v.optional(v.number()),
y: v.optional(v.number()),
data: v.optional(v.any()),
}),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
// ゲーム状態を取得
const session = await ctx.db.get(args.sessionId);
if (!session) throw new Error("Session not found");
// アクションを記録
await ctx.db.insert("gameActions", {
sessionId: args.sessionId,
playerId: identity.subject,
action: args.action,
timestamp: Date.now(),
});
// ゲームロジックの実行(簡略版)
// 実際のゲームでは、ここでゲーム状態を更新します
return { success: true };
},
});
プレイヤーのアクションは即座に他のプレイヤーに配信され、リアルタイムな対戦が実現できます。
typescript// ゲーム状態をリアルタイムで取得
export const getGameState = query({
args: {
sessionId: v.id("gameSessions"),
},
handler: async (ctx, args) => {
const session = await ctx.db.get(args.sessionId);
// 最新のアクションを取得
const recentActions = await ctx.db
.query("gameActions")
.filter((q) => q.eq(q.field("sessionId"), args.sessionId))
.order("desc")
.take(50);
return {
session,
recentActions,
};
},
});
実装のポイント:
- すべてのプレイヤーアクションがリアルタイムで他のプレイヤーに同期されます
- Convexのトランザクション機能により、同時アクセスでもデータの整合性が保たれます
- ゲームロジックをバックエンド(Convex関数)で実装することで、チート対策にもなります
AIアプリケーション
RAG(Retrieval-Augmented Generation)の実装
ConvexはベクトルデータベースとAIアプリケーション開発の相性が良いです。
typescript// convex/documents.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api } from "./_generated/api";
// ドキュメントを埋め込みベクトル化して保存
export const embedDocument = action({
args: {
text: v.string(),
},
handler: async (ctx, args) => {
// OpenAI APIで埋め込みベクトルを生成
const response = await fetch("https://api.openai.com/v1/embeddings", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "text-embedding-3-small",
input: args.text,
}),
});
const data = await response.json();
const embedding = data.data[0].embedding;
// Convexデータベースに保存
await ctx.runMutation(api.documents.saveWithEmbedding, {
text: args.text,
embedding,
});
},
});
Actions機能を使うことで、外部APIとの連携が可能になります。
typescript// convex/search.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
// ベクトル検索でドキュメントを取得
export const vectorSearch = query({
args: {
queryEmbedding: v.array(v.number()),
limit: v.optional(v.number()),
},
handler: async (ctx, args) => {
// ベクトル検索を実行
const results = await ctx.db
.query("documents")
.withSearchIndex("by_embedding", (q) =>
q.search("embedding", args.queryEmbedding)
)
.take(args.limit ?? 10);
return results;
},
});
Convexのベクトル検索機能により、意味的に類似したドキュメントを効率的に検索できます。
フルテキスト検索の実装
BM25スコアリングによるフルテキスト検索も簡単に実装できます。
typescript// convex/search.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
// テキスト検索
export const fullTextSearch = query({
args: {
searchTerm: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db
.query("documents")
.withSearchIndex("search_text", (q) =>
q.search("text", args.searchTerm)
)
.collect();
},
});
スキーマでインデックスを定義するだけで、高度な検索機能が利用できます。
typescript// convex/schema.ts
export default defineSchema({
documents: defineTable({
text: v.string(),
embedding: v.optional(v.array(v.number())),
createdAt: v.number(),
})
.searchIndex("search_text", {
searchField: "text",
})
.vectorIndex("by_embedding", {
vectorField: "embedding",
dimensions: 1536, // text-embedding-3-smallの次元数
}),
});
まとめ
Convexは、リアルタイム性が求められるモダンなアプリケーション開発を劇的に簡素化するバックエンドプラットフォームです。本記事では、チャット、コラボレーションツール、SaaSプロダクト、ゲーム、AIアプリケーションという主要なユースケースを紹介しました。
Convexの主な利点
開発体験の向上 TypeScriptで統一された開発体験により、フロントエンドからバックエンドまで一貫した型安全性を保てます。
インフラ管理不要 サーバーレスアーキテクチャにより、スケーリングや監視、メンテナンスが自動化されています。
リアルタイム機能の簡単実装 WebSocketの複雑な実装を気にせず、宣言的なAPIでリアルタイム同期が実現できます。
データ整合性の保証 ACIDトランザクションにより、複雑な競合制御を実装することなくデータの整合性が保たれます。
適用を検討すべきケース
Convexは以下のようなケースで特に威力を発揮します。
- リアルタイム性が必須のアプリケーション(チャット、コラボレーション)
- 迅速なMVP開発が必要なスタートアップ
- TypeScript/React/Next.jsを主要技術スタックとするプロジェクト
- バックエンドの運用コストを削減したいチーム
考慮すべき点
一方で、以下の点には注意が必要です。
ベンダーロックイン Convex独自のAPIや設計思想に依存するため、他のプラットフォームへの移行は容易ではありません。
既存システムとの統合 すでに大規模なバックエンドシステムがある場合、部分的な導入が難しい可能性があります。
コスト構造の確認 利用規模が大きくなった際のコストを事前に確認しておくことをお勧めします。
Convexは、モダンなWebアプリケーション開発において、開発者体験と実装スピードを大幅に向上させる強力なツールです。特にリアルタイム機能が必要なプロジェクトでは、従来の開発手法と比較して圧倒的な生産性を実現できるでしょう。
ぜひ、本記事で紹介したユースケースを参考に、Convexを活用した開発を検討してみてください。
関連リンク
articleConvex で実現できること早見:チャット・コラボ・SaaS・ゲームの主要ユースケース総覧
articleConvex 運用監視ダッシュボード構築:Datadog/Grafana 連携と SLO 設計
articleConvex で「Permission denied」多発時の原因特定:認可/コンテキスト/引数を総点検
articleConvex でリアルタイムダッシュボード:KPI/閾値アラート/役割別ビューの実装例
articleConvex で Presence(在席)機能を実装:ユーザーステータスのリアルタイム同期
articleConvex で実践する CQRS/イベントソーシング:履歴・再生・集約の設計ガイド
articleDevin vs 手動コードレビュー:バグ検出率と回帰不具合の実地検証レポート
articleLodash 文字列ユーティリティ早見表:case 変換・パディング・トリムの極意
articleLlamaIndex と LangChain を徹底比較:開発速度・可観測性・精度ベンチ
articleConvex で実現できること早見:チャット・コラボ・SaaS・ゲームの主要ユースケース総覧
articleLangChain 再ランキング手法の実測:Cohere/OpenAI ReRank/Cross-Encoder の効果
articleJotai 非同期で Suspense が発火しない問題の切り分けガイド
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来