Vue.js で API 通信を極める:Axios 徹底活用術

Web 開発における API 通信は、まるで人と人とのコミュニケーションのようなものです。相手の話を聞き、適切に返答し、時には誤解を解いたり、エラーを修正したりする必要があります。Vue.js と Axios を使った API 通信もまさに同じで、サーバーとの「会話」を円滑に進めるための技術と心構えが必要なのです。
今回の記事では、Vue.js 初心者から中級者の方々が、Axios を使った API 通信をマスターできるよう、実践的な内容を段階的にお伝えしていきます。実際のエラーコードや解決策も含めて、皆さんの開発現場で即戦力となる知識をお届けします。
背景 - なぜ Vue.js で Axios が選ばれるのか
Vue.js エコシステムにおいて、Axios が HTTP クライアントライブラリとして圧倒的な支持を得ている理由は、その直感的な API 設計と豊富な機能にあります。
Vue.js 開発者が Axios を選ぶ理由
# | 理由 | 具体的なメリット |
---|---|---|
1 | Promise ベースの API | async/await での直感的な非同期処理 |
2 | インターセプター機能 | リクエスト・レスポンスの前処理・後処理 |
3 | 自動的な JSON 変換 | データの送受信が簡単 |
4 | エラーハンドリング | 統一されたエラー処理 |
5 | ブラウザ・Node.js 対応 | 開発・本番環境での一貫性 |
Vue.js の reactive なデータバインディングと Axios の Promise ベースの API は、まさに天と地が結ばれるような相性の良さを見せます。データの取得から画面への反映までが、自然な流れで実装できるのです。
従来の fetch API との比較
javascript// fetch API での実装例
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status}`
);
}
const data = await response.json();
this.users = data;
} catch (error) {
console.error('Error:', error);
}
上記の fetch API による実装は、エラーハンドリングやデータの変換を手動で行う必要があります。一方、Axios を使用した場合は、より簡潔で読みやすいコードになります。
課題 - Vue.js での API 通信でよくある問題
Vue.js 開発者が API 通信で直面する課題は、技術的な問題だけでなく、開発者の心理的な負担も含まれています。
頻繁に発生する問題とその影響
1. CORS (Cross-Origin Resource Sharing) エラー
多くの初心者が最初に遭遇するエラーです。ブラウザのコンソールに以下のようなエラーが表示されます:
csharpAccess to XMLHttpRequest at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
2. 非同期処理の理解不足
Vue.js のライフサイクルと非同期処理の組み合わせで、以下のような問題が発生します:
javascript// 問題のあるコード例
export default {
data() {
return {
users: [],
};
},
created() {
// この時点では users は空の配列
this.fetchUsers();
console.log(this.users); // [] が出力される
},
methods: {
async fetchUsers() {
this.users = await axios.get('/api/users');
},
},
};
3. エラーハンドリングの不備
適切なエラーハンドリングを行わないと、ユーザーは何が起こったのかわからず、混乱してしまいます。
これらの課題は、開発者にとって大きなストレスとなり、時には自信を失う原因にもなります。しかし、適切な知識と実践があれば、これらの問題は必ず解決できるのです。
解決策 - Axios の基本概念と導入方法
Axios は、これらの課題を解決するための強力なツールです。まずは基本的な導入から始めましょう。
プロジェクトへの Axios 導入
Vue.js プロジェクトに Axios を導入する方法をご紹介します。Yarn を使用してパッケージをインストールしましょう。
bash# Axios のインストール
yarn add axios
# TypeScript を使用する場合(型定義も自動的に含まれます)
yarn add axios
基本的な設定とインスタンス作成
API との通信を効率的に行うために、Axios インスタンスを作成して設定を一元管理します。
javascript// src/services/api.js
import axios from 'axios';
// Axios インスタンスを作成
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000, // 10秒でタイムアウト
headers: {
'Content-Type': 'application/json',
},
});
export default apiClient;
この設定により、すべての API 通信で共通の設定を使用できるようになります。baseURL を設定することで、各リクエストで完全な URL を指定する必要がなくなり、保守性が向上します。
Vue.js コンポーネントでの基本的な使用方法
Vue.js コンポーネント内で Axios を使用する基本的なパターンをご紹介します。
javascript// src/components/UserList.vue
<template>
<div class="user-list">
<h2>ユーザー一覧</h2>
<div v-if="loading" class="loading">読み込み中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
</template>
<script>
import apiClient from '@/services/api';
export default {
name: 'UserList',
data() {
return {
users: [],
loading: false,
error: null
};
},
async created() {
await this.fetchUsers();
},
methods: {
async fetchUsers() {
this.loading = true;
this.error = null;
try {
const response = await apiClient.get('/users');
this.users = response.data;
} catch (error) {
this.error = 'ユーザーの取得に失敗しました';
console.error('Error fetching users:', error);
} finally {
this.loading = false;
}
}
}
};
</script>
このコードでは、loading 状態、エラー状態、成功状態を適切に管理しています。ユーザーにとって分かりやすい UI を提供することは、技術的な実装と同じくらい重要です。
具体例
Axios の基本的な使い方
Axios の基本的な使い方を、実践的な例とともに詳しく見ていきましょう。
GET リクエストの実装
データの取得は、最も基本的で頻繁に使用される操作です。
javascript// src/services/userService.js
import apiClient from '@/services/api';
export const userService = {
// 全ユーザーの取得
async getAllUsers() {
try {
const response = await apiClient.get('/users');
return {
success: true,
data: response.data,
message: 'ユーザーの取得に成功しました',
};
} catch (error) {
return {
success: false,
data: null,
message: this.handleError(error),
};
}
},
// 特定ユーザーの取得
async getUserById(userId) {
try {
const response = await apiClient.get(
`/users/${userId}`
);
return {
success: true,
data: response.data,
};
} catch (error) {
throw new Error(
`ユーザー取得エラー: ${error.message}`
);
}
},
// エラーハンドリング用のヘルパー関数
handleError(error) {
if (error.response) {
// サーバーからのレスポンスがある場合
return `サーバーエラー: ${error.response.status} - ${error.response.data.message}`;
} else if (error.request) {
// リクエストは送信されたが、レスポンスがない場合
return 'ネットワークエラー: サーバーに接続できません';
} else {
// その他のエラー
return `エラー: ${error.message}`;
}
},
};
POST リクエストの実装
新しいデータを作成する際の POST リクエストの実装例です。
javascript// 新規ユーザーの作成
async createUser(userData) {
try {
const response = await apiClient.post('/users', {
name: userData.name,
email: userData.email,
password: userData.password
});
return {
success: true,
data: response.data,
message: '新規ユーザーを作成しました'
};
} catch (error) {
// バリデーションエラーの詳細処理
if (error.response && error.response.status === 422) {
const validationErrors = error.response.data.errors;
return {
success: false,
errors: validationErrors,
message: 'バリデーションエラーが発生しました'
};
}
throw error;
}
}
Vue.js プロジェクトへの統合
Vue.js プロジェクトに Axios を統合する際の、実践的なアプローチをご紹介します。
プラグインとしての統合
javascript// src/plugins/axios.js
import axios from 'axios';
const axiosPlugin = {
install(Vue, options) {
// Axios インスタンスの作成
const apiClient = axios.create({
baseURL: options.baseURL || 'https://api.example.com',
timeout: options.timeout || 10000,
headers: {
'Content-Type': 'application/json',
},
});
// リクエストインターセプター
apiClient.interceptors.request.use(
(config) => {
// 認証トークンの自動付与
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// レスポンスインターセプター
apiClient.interceptors.response.use(
(response) => response,
(error) => {
// 認証エラーの場合は自動的にログイン画面に遷移
if (
error.response &&
error.response.status === 401
) {
localStorage.removeItem('authToken');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// Vue インスタンスにAPIクライアントを追加
Vue.prototype.$api = apiClient;
},
};
export default axiosPlugin;
main.js での設定
javascript// src/main.js
import Vue from 'vue';
import App from './App.vue';
import axiosPlugin from '@/plugins/axios';
// Axios プラグインの使用
Vue.use(axiosPlugin, {
baseURL:
process.env.VUE_APP_API_BASE_URL ||
'https://api.example.com',
timeout: 15000,
});
new Vue({
render: (h) => h(App),
}).$mount('#app');
Composition API での使用
Vue 3 の Composition API を使用した場合の実装例です。
javascript// src/composables/useApi.js
import { ref, reactive } from 'vue';
import axios from 'axios';
export function useApi() {
const loading = ref(false);
const error = ref(null);
const apiState = reactive({
data: null,
error: null,
loading: false,
});
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
});
const request = async (config) => {
apiState.loading = true;
apiState.error = null;
try {
const response = await apiClient(config);
apiState.data = response.data;
return response.data;
} catch (err) {
apiState.error = err.message;
throw err;
} finally {
apiState.loading = false;
}
};
const get = (url, config = {}) =>
request({ method: 'get', url, ...config });
const post = (url, data, config = {}) =>
request({ method: 'post', url, data, ...config });
const put = (url, data, config = {}) =>
request({ method: 'put', url, data, ...config });
const del = (url, config = {}) =>
request({ method: 'delete', url, ...config });
return {
apiState,
loading,
error,
get,
post,
put,
delete: del,
};
}
GET/POST/PUT/DELETE 操作の実装
RESTful API の基本的な CRUD 操作を、実践的な例とともに詳しく見ていきましょう。
GET 操作の詳細実装
javascript// src/services/postService.js
import apiClient from '@/services/api';
export const postService = {
// 全投稿の取得(ページネーション対応)
async getAllPosts(page = 1, limit = 10) {
try {
const response = await apiClient.get('/posts', {
params: { page, limit },
});
return {
success: true,
data: response.data.posts,
pagination: {
currentPage: response.data.currentPage,
totalPages: response.data.totalPages,
totalItems: response.data.totalItems,
},
};
} catch (error) {
console.error('投稿取得エラー:', error);
throw new Error('投稿の取得に失敗しました');
}
},
// 検索機能付きの投稿取得
async searchPosts(searchQuery, filters = {}) {
try {
const response = await apiClient.get(
'/posts/search',
{
params: {
q: searchQuery,
category: filters.category,
sortBy: filters.sortBy || 'createdAt',
order: filters.order || 'desc',
},
}
);
return response.data;
} catch (error) {
if (error.response?.status === 404) {
return {
posts: [],
message: '検索結果が見つかりませんでした',
};
}
throw error;
}
},
};
POST 操作の詳細実装
javascript// 新規投稿の作成
async createPost(postData) {
try {
const response = await apiClient.post('/posts', {
title: postData.title,
content: postData.content,
category: postData.category,
tags: postData.tags || [],
isPublished: postData.isPublished || false
});
return {
success: true,
data: response.data,
message: '投稿を作成しました'
};
} catch (error) {
// バリデーションエラーの詳細処理
if (error.response?.status === 422) {
const errors = error.response.data.errors;
return {
success: false,
errors: errors,
message: '入力内容に問題があります'
};
}
// 認証エラー
if (error.response?.status === 401) {
throw new Error('認証が必要です。ログインしてください。');
}
// その他のエラー
throw new Error(`投稿の作成に失敗しました: ${error.message}`);
}
}
PUT 操作の詳細実装
javascript// 既存投稿の更新
async updatePost(postId, postData) {
try {
const response = await apiClient.put(`/posts/${postId}`, {
title: postData.title,
content: postData.content,
category: postData.category,
tags: postData.tags,
isPublished: postData.isPublished,
updatedAt: new Date().toISOString()
});
return {
success: true,
data: response.data,
message: '投稿を更新しました'
};
} catch (error) {
// 404エラー(投稿が見つからない)
if (error.response?.status === 404) {
throw new Error('指定された投稿が見つかりません');
}
// 403エラー(権限なし)
if (error.response?.status === 403) {
throw new Error('この投稿を編集する権限がありません');
}
throw new Error(`投稿の更新に失敗しました: ${error.message}`);
}
}
DELETE 操作の詳細実装
javascript// 投稿の削除
async deletePost(postId) {
try {
await apiClient.delete(`/posts/${postId}`);
return {
success: true,
message: '投稿を削除しました'
};
} catch (error) {
// 404エラー
if (error.response?.status === 404) {
throw new Error('指定された投稿が見つかりません');
}
// 403エラー
if (error.response?.status === 403) {
throw new Error('この投稿を削除する権限がありません');
}
throw new Error(`投稿の削除に失敗しました: ${error.message}`);
}
}
Vue.js コンポーネントでの使用例
javascript// src/components/PostManager.vue
<template>
<div class="post-manager">
<h2>投稿管理</h2>
<!-- 新規投稿フォーム -->
<form @submit.prevent="createPost" class="post-form">
<input
v-model="newPost.title"
type="text"
placeholder="タイトル"
required
/>
<textarea
v-model="newPost.content"
placeholder="内容"
required
></textarea>
<button type="submit" :disabled="isSubmitting">
{{ isSubmitting ? '投稿中...' : '投稿する' }}
</button>
</form>
<!-- 投稿一覧 -->
<div class="posts-list">
<article
v-for="post in posts"
:key="post.id"
class="post-item"
>
<h3>{{ post.title }}</h3>
<p>{{ post.content }}</p>
<div class="post-actions">
<button @click="editPost(post)">編集</button>
<button @click="deletePost(post.id)" class="delete-btn">削除</button>
</div>
</article>
</div>
</div>
</template>
<script>
import { postService } from '@/services/postService';
export default {
name: 'PostManager',
data() {
return {
posts: [],
newPost: {
title: '',
content: ''
},
isSubmitting: false,
error: null
};
},
async created() {
await this.fetchPosts();
},
methods: {
async fetchPosts() {
try {
const result = await postService.getAllPosts();
this.posts = result.data;
} catch (error) {
this.error = error.message;
}
},
async createPost() {
this.isSubmitting = true;
try {
const result = await postService.createPost(this.newPost);
if (result.success) {
this.posts.unshift(result.data);
this.newPost = { title: '', content: '' };
this.$emit('success', result.message);
}
} catch (error) {
this.error = error.message;
} finally {
this.isSubmitting = false;
}
}
}
};
</script>
インターセプターの活用
インターセプターは、リクエストやレスポンスを自動的に処理する強力な機能です。認証、ログ、エラー処理などを一元化できます。
リクエストインターセプターの実装
javascript// src/interceptors/request.js
import apiClient from '@/services/api';
// リクエストインターセプターの設定
apiClient.interceptors.request.use(
(config) => {
// 認証トークンの自動付与
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// リクエストログの出力
console.log(
`[REQUEST] ${config.method?.toUpperCase()} ${
config.url
}`
);
// タイムスタンプの追加
config.metadata = { startTime: new Date() };
return config;
},
(error) => {
console.error('[REQUEST ERROR]', error);
return Promise.reject(error);
}
);
レスポンスインターセプターの実装
javascript// レスポンスインターセプターの設定
apiClient.interceptors.response.use(
(response) => {
// レスポンス時間の計算
const endTime = new Date();
const duration =
endTime - response.config.metadata.startTime;
console.log(
`[RESPONSE] ${response.config.method?.toUpperCase()} ${
response.config.url
} - ${duration}ms`
);
return response;
},
(error) => {
// エラーレスポンスの詳細処理
if (error.response) {
const { status, data } = error.response;
switch (status) {
case 401:
// 認証エラー:自動的にログアウト
localStorage.removeItem('authToken');
window.location.href = '/login';
break;
case 403:
// 権限エラー
console.error(
'[FORBIDDEN] アクセス権限がありません'
);
break;
case 404:
// Not Found エラー
console.error(
'[NOT FOUND] リソースが見つかりません'
);
break;
case 422:
// バリデーションエラー
console.error('[VALIDATION ERROR]', data.errors);
break;
case 500:
// サーバーエラー
console.error(
'[SERVER ERROR] サーバーで問題が発生しました'
);
break;
default:
console.error(`[ERROR ${status}]`, data.message);
}
} else if (error.request) {
// ネットワークエラー
console.error(
'[NETWORK ERROR] サーバーに接続できません'
);
}
return Promise.reject(error);
}
);
高度なインターセプター:リトライ機能
javascript// src/interceptors/retry.js
import apiClient from '@/services/api';
// リトライ機能付きのインターセプター
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// リトライ条件の判定
if (
error.response?.status >= 500 &&
!originalRequest._retry &&
originalRequest.retryCount < 3
) {
originalRequest._retry = true;
originalRequest.retryCount =
(originalRequest.retryCount || 0) + 1;
// 指数バックオフでリトライ
const delay =
Math.pow(2, originalRequest.retryCount) * 1000;
await new Promise((resolve) =>
setTimeout(resolve, delay)
);
console.log(
`[RETRY] ${
originalRequest.retryCount
}/3 - ${originalRequest.method?.toUpperCase()} ${
originalRequest.url
}`
);
return apiClient(originalRequest);
}
return Promise.reject(error);
}
);
エラーハンドリングの実装
適切なエラーハンドリングは、ユーザーエクスペリエンスを大きく左右します。実際のエラーコードとともに、包括的なエラーハンドリング戦略をご紹介します。
包括的なエラーハンドリングクラス
javascript// src/utils/errorHandler.js
export class ApiError extends Error {
constructor(message, status, data) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data;
}
}
export const errorHandler = {
// HTTP ステータスコードに基づくエラー処理
handleHttpError(error) {
if (!error.response) {
return {
type: 'NETWORK_ERROR',
message:
'ネットワークエラーが発生しました。インターネット接続を確認してください。',
userMessage: 'インターネット接続を確認してください',
};
}
const { status, data } = error.response;
switch (status) {
case 400:
return {
type: 'BAD_REQUEST',
message: 'リクエストの内容に問題があります',
userMessage: '入力内容を確認してください',
details: data.errors || [],
};
case 401:
return {
type: 'UNAUTHORIZED',
message: '認証が必要です',
userMessage: 'ログインしてください',
action: 'redirect_login',
};
case 403:
return {
type: 'FORBIDDEN',
message: 'アクセス権限がありません',
userMessage: 'この操作を実行する権限がありません',
};
case 404:
return {
type: 'NOT_FOUND',
message: 'リソースが見つかりません',
userMessage:
'お探しのページまたはデータが見つかりません',
};
case 422:
return {
type: 'VALIDATION_ERROR',
message: 'バリデーションエラーが発生しました',
userMessage: '入力内容に問題があります',
details: data.errors || [],
};
case 429:
return {
type: 'RATE_LIMIT',
message: 'リクエストが多すぎます',
userMessage:
'しばらく時間をおいてから再度お試しください',
};
case 500:
return {
type: 'SERVER_ERROR',
message: 'サーバーで問題が発生しました',
userMessage:
'サーバーで問題が発生しました。しばらく時間をおいてから再度お試しください',
};
default:
return {
type: 'UNKNOWN_ERROR',
message: `予期しないエラーが発生しました (${status})`,
userMessage: '予期しないエラーが発生しました',
};
}
},
// Vue.js コンポーネントでの使用を想定したエラーハンドラー
async handleApiCall(apiCall, context) {
try {
context.loading = true;
context.error = null;
const result = await apiCall();
return result;
} catch (error) {
const errorInfo = this.handleHttpError(error);
context.error = errorInfo;
// 自動的にログイン画面に遷移
if (errorInfo.action === 'redirect_login') {
context.$router.push('/login');
}
throw new ApiError(
errorInfo.message,
error.response?.status,
errorInfo
);
} finally {
context.loading = false;
}
},
};
Vue.js コンポーネントでの使用例
javascript// src/components/UserProfile.vue
<template>
<div class="user-profile">
<h2>ユーザープロフィール</h2>
<!-- ローディング状態 -->
<div v-if="loading" class="loading">
<div class="spinner"></div>
<p>読み込み中...</p>
</div>
<!-- エラー状態 -->
<div v-else-if="error" class="error-container">
<div class="error-message">
<h3>エラーが発生しました</h3>
<p>{{ error.userMessage }}</p>
<!-- バリデーションエラーの詳細表示 -->
<div v-if="error.details && error.details.length > 0" class="error-details">
<h4>詳細:</h4>
<ul>
<li v-for="detail in error.details" :key="detail.field">
{{ detail.field }}: {{ detail.message }}
</li>
</ul>
</div>
<button @click="retryLoad" class="retry-btn">再試行</button>
</div>
</div>
<!-- 正常状態 -->
<div v-else class="profile-content">
<div class="profile-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
<form @submit.prevent="updateProfile" class="profile-form">
<input v-model="editUser.name" type="text" placeholder="名前" />
<input v-model="editUser.email" type="email" placeholder="メールアドレス" />
<button type="submit" :disabled="isUpdating">
{{ isUpdating ? '更新中...' : '更新' }}
</button>
</form>
</div>
</div>
</template>
<script>
import { userService } from '@/services/userService';
import { errorHandler } from '@/utils/errorHandler';
export default {
name: 'UserProfile',
data() {
return {
user: null,
editUser: { name: '', email: '' },
loading: false,
error: null,
isUpdating: false
};
},
async created() {
await this.loadUserProfile();
},
methods: {
async loadUserProfile() {
try {
await errorHandler.handleApiCall(
() => userService.getCurrentUser(),
this
);
this.user = await userService.getCurrentUser();
this.editUser = { ...this.user };
} catch (error) {
// エラーハンドリングは errorHandler で処理済み
console.error('ユーザー情報の読み込みに失敗:', error);
}
},
async updateProfile() {
this.isUpdating = true;
try {
await errorHandler.handleApiCall(
() => userService.updateUser(this.editUser),
this
);
this.user = await userService.updateUser(this.editUser);
this.$emit('success', 'プロフィールを更新しました');
} catch (error) {
console.error('プロフィール更新に失敗:', error);
} finally {
this.isUpdating = false;
}
},
async retryLoad() {
await this.loadUserProfile();
}
}
};
</script>
グローバルエラーハンドラーの設定
javascript// src/main.js
import Vue from 'vue';
import App from './App.vue';
import { errorHandler } from '@/utils/errorHandler';
// グローバルエラーハンドラーの設定
Vue.config.errorHandler = (err, vm, info) => {
console.error('グローバルエラー:', err);
console.error('コンポーネント:', vm);
console.error('詳細情報:', info);
// エラー報告サービスにエラーを送信
// 例: Sentry, LogRocket など
};
// プロトタイプにエラーハンドラーを追加
Vue.prototype.$handleError = errorHandler.handleHttpError;
new Vue({
render: (h) => h(App),
}).$mount('#app');
まとめ
Vue.js と Axios を使った API 通信の世界を探求する旅は、いかがでしたでしょうか。この記事では、基本的な使い方から高度なエラーハンドリングまで、実践的な内容を段階的にお伝えしました。
重要なポイントの振り返り
# | ポイント | 価値 |
---|---|---|
1 | 適切なエラーハンドリング | ユーザーエクスペリエンスの向上 |
2 | インターセプターの活用 | 効率的な認証・ログ処理 |
3 | 一元化された API 設定 | 保守性の向上 |
4 | 型安全性の確保 | バグの早期発見 |
5 | レスポンシブな UI 状態管理 | 直感的なユーザーインターフェース |
API 通信は、単なる技術的な処理ではありません。それは、あなたのアプリケーションとユーザーを繋ぐ大切な架け橋です。エラーが発生したときに適切なメッセージを表示し、ローディング状態を分かりやすく示し、成功時には喜びを共有する。そんな心のこもった実装こそが、本当に価値のあるアプリケーションを作り上げるのです。
次のステップ
今回学んだ内容を基に、ぜひ実際のプロジェクトで実践してみてください。最初は小さな機能から始めて、徐々に複雑な処理にチャレンジしていきましょう。
- GraphQL との組み合わせを検討してみる
- WebSocket を使ったリアルタイム通信に挑戦
- Progressive Web App (PWA) での API 通信を実装
- テスト駆動開発 でより堅牢なコードを書く
皆さんの開発活動が、より豊かで創造的なものになることを心より願っています。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来