1. はじめに
React初心者の皆さん、ようこそ!
今回は GraphQL × React × TypeScript を使って「GitHubユーザー検索アプリ」を作ります。
本記事は、ただコードを写すだけではなく
- なぜそのコードが必要なのか(思考プロセス)
- ReactやTypeScriptの書き方のポイント(記法解説)
を両方押さえることを目的としています。
完成まで30分。
記事を読み進めながら手を動かせば、GraphQL API連携とReactのProps/Stateの基礎が一度に学べます。
2. アプリ概要と完成イメージ
作るアプリの機能はシンプルです。
- ユーザー名を入力して検索ボタンを押す
- GitHub GraphQL APIを使って該当ユーザーを取得
- アバターとユーザー名を一覧で表示

3. 開発環境の準備
Node.js と npm の確認
node -v
npm -v
どちらもバージョンが表示されればOKです。
ViteでReact+TypeScriptプロジェクト作成
npm create vite@latest github-user-search -- --template react-ts
cd github-user-search
npm install
必要なライブラリを追加
npm install @apollo/client graphql
4. 思考プロセス:設計とデータの流れ

Reactアプリを作るときは、まず「どのコンポーネントが何を担当するか」を決めておくと、後の実装がスムーズになります。今回は以下の3つに分割します。
- App.tsx(親コンポーネント)
- 検索キーワードの状態管理
- GraphQLクエリの実行(Apollo Client)
- 子コンポーネントへのデータ渡し
- SearchForm.tsx(子コンポーネント)
- 入力欄と検索ボタンのUI
- 入力内容を親に渡すためのイベント処理
- UserList.tsx(子コンポーネント)
- 検索結果(ユーザー一覧)の表示
- 各ユーザー情報のレイアウト
データの流れ
- ユーザーが SearchForm でキーワードを入力し、検索ボタンをクリック。
- SearchForm から入力値が App に渡される。
- App が GraphQL クエリを実行し、GitHub API からユーザー情報を取得。
- 取得結果を UserList に渡し、リストとして表示。
この流れにより、「状態は親で管理」し、子は「見た目とイベントのみ」を担当するシンプルな構造になります。
ディレクトリ構成
src/
├── components/
│ ├── SearchForm.tsx
│ └── UserList.tsx
├── apolloClient.ts
├── queries.ts
├── App.tsx
└── main.tsx
components/
フォルダには再利用可能なUI部品(子コンポーネント)をまとめます。apolloClient.ts
はApollo Clientの設定。queries.ts
はGraphQLクエリの定義。App.tsx
はアプリの中核(状態・データ取得)。main.tsx
はReactアプリのエントリーポイント。
5. GraphQLとGitHub APIの準備
GitHub Personal Access Tokenの取得
- GitHubにログイン
- [Settings] → [Developer settings] → [Personal Access Tokens]
- New Token を作成(
read:user
権限を付与) - 発行されたトークンをコピー
.env
ファイルの作成
取得したトークンはソースコードに直接書かず、環境変数で管理します。
プロジェクトのルートディレクトリに .env
ファイルを作成し、以下のように記述してください。
VITE_GITHUB_TOKEN=取得したトークン
VITE_
から始まる変数名にすると、Vite経由でReactアプリから参照可能になります。
また、.env
は Git管理対象外 にするため、.gitignore
に追加してください。
6. Apollo Clientのセットアップ
コード全文
src/apolloClient.ts
import { ApolloClient, InMemoryCache } from "@apollo/client";
const GITHUB_API_URL = "https://api.github.com/graphql";
const GITHUB_TOKEN = import.meta.env.VITE_GITHUB_TOKEN;
export const client = new ApolloClient({
uri: GITHUB_API_URL,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${GITHUB_TOKEN}`,
},
});
解説ポイント
1. インポート
import { ApolloClient, InMemoryCache } from "@apollo/client";
- ApolloClient:GraphQL サーバーとの通信・キャッシュ・エラーハンドリングを担うクライアント本体。
- InMemoryCache:取得したデータをメモリ上にキャッシュする仕組み。再取得の削減や UI の即時反映に効きます。
2. API エンドポイントとトークン
const GITHUB_API_URL = "https://api.github.com/graphql";
const GITHUB_TOKEN = import.meta.env.VITE_GITHUB_TOKEN;
- GITHUB_API_URL は GitHub の GraphQL エンドポイント。
- GITHUB_TOKEN は
.env
に保存した 環境変数 を参照(Vite ではVITE_
プレフィックスが必須)。
3. クライアント生成
export const client = new ApolloClient({
uri: GITHUB_API_URL,
cache: new InMemoryCache(),
headers: {
Authorization: `Bearer ${GITHUB_TOKEN}`,
},
});
- uri:通信先の GraphQL サーバー(ここでは GitHub)。
- cache:
new InMemoryCache()
を指定すると、同じクエリ結果をキャッシュから即座に再利用できます。 - headers.Authorization:
Bearer <token>
形式で認証ヘッダを付与。GitHub GraphQL API は認証必須です。
トークン未設定だと 401 Unauthorized が発生します。
トークン権限は read:user
程度で十分です(必要に応じて拡張)。
7. GraphQLクエリの定義
コード全文
src/queries.ts
import { gql } from "@apollo/client";
export const SEARCH_USERS = gql`
query SearchUsers($query: String!) {
search(query: $query, type: USER, first: 10) {
nodes {
... on User {
id
login
avatarUrl
}
}
}
}
`;
解説ポイント
1.gql
タグでクエリを定義
import { gql } from "@apollo/client";
gql
はGraphQL 文法を文字列内で表すためのタグ関数。Apollo がパースしてクエリ AST に変換します。
2. 変数付きクエリの宣言
query SearchUsers($query: String!) { ... }
SearchUsers
はクエリの名前(任意)で、デバッグ時に特定しやすくなります。$query: String!
は「必須の文字列型の変数$query
」を受け取るという意味。
実行時に{ variables: { query: "keyword" } }
のように渡します。
3. GitHub 検索フィールド search
search(query: $query, type: USER, first: 10) { ... }
query: $query
:ユーザーが入力した検索キーワード。type: USER
:検索対象を「ユーザー」に限定。first: 10
:最初の 10 件を取得(ページネーションの一種)。
もっと欲しいときはafter
引数を加えて次ページを取得可能(pageInfo { endCursor hasNextPage }
を展開)。
4. nodes
と型分岐(インラインフラグメント)
nodes {
... on User {
id
login
avatarUrl
}
}
search
は複数の型(Issue, Repository, User など)を返しうるため、... on User
で「User のときだけこれらのフィールドを取得して」と指定します。- 取得しているのは UI 表示に必要な最小限:
- id:React の
key
に使える一意 ID - login:ユーザー名
- avatarUrl:アバター画像 URL
- id:React の
名前や Bio、フォロワー数なども必要なら name
, bio
, followers { totalCount }
を追加できます。必要なものだけを明示して取得するのが GraphQL の思想です。
8. App.tsx(親コンポーネント)
コード全文
src/App.tsx
import React, { useState } from "react";
import { ApolloProvider, useLazyQuery } from "@apollo/client";
import { client } from "./apolloClient";
import { SEARCH_USERS } from "./queries";
import SearchForm from "./components/SearchForm";
import UserList from "./components/UserList";
function App() {
return (
<ApolloProvider client={client}>
<InnerApp />
</ApolloProvider>
);
}
function InnerApp() {
const [searchUsers, { loading, error }] = useLazyQuery(SEARCH_USERS);
const [users, setUsers] = useState<any[]>([]);
const handleSearch = (keyword: string) => {
searchUsers({ variables: { query: keyword } }).then((res) => {
setUsers(res.data?.search?.nodes ?? []);
});
};
return (
<div style={{ minHeight: "100vh", padding: 24 }}>
<h1>GitHubユーザー検索</h1>
<SearchForm onSearch={handleSearch} />
{loading && <p>読み込み中...</p>}
{error && <p>エラーが発生しました</p>}
<UserList users={users} />
</div>
);
}
export default App;
解説ポイント
1. useLazyQueryの使い方
const [searchUsers, { data, loading, error }] = useLazyQuery(SEARCH_USERS);
useLazyQuery
は、コンポーネント描画時ではなく「特定のタイミングで」クエリを実行したいときに使います。- 第一要素
searchUsers
はクエリを発火する関数。 - 第二要素は
{ data, loading, error }
というオブジェクトで、クエリの状態を保持します。
2. 親から子へ関数を渡す
<SearchForm onSearch={handleSearch} />
SearchForm
は、ユーザーが入力した検索ワードを親に伝えるためのPropsonSearch
を受け取ります。- Reactでは「子→親」方向のデータ伝達は直接できないため、このように「親から関数を渡し、子がそれを呼び出す」という形をとります。
3. ApolloProviderで全体をラップ
<ApolloProvider client={client}> ... </ApolloProvider>
ApolloProvider
はReactのコンテキストAPIを利用して、子コンポーネント全体でApollo Clientのインスタンスを利用可能にします。client
は先ほど作成したapolloClient.ts
の設定を使っています。
9. SearchForm.tsx(検索フォーム)
コード全文
src/components/SearchForm.tsx
import React, { useState } from "react";
type Props = {
onSearch: (keyword: string) => void;
};
function SearchForm({ onSearch }: Props) {
const [input, setInput] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
onSearch(input);
};
return (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="GitHubユーザー名"
/>
<button type="submit">検索</button>
</form>
);
}
export default SearchForm;
解説ポイント
1. Propsの型定義
type Props = {
onSearch: (keyword: string) => void;
};
- 親から渡される
onSearch
関数の型を定義。 - 引数は
string
(検索キーワード)、戻り値はvoid
(値を返さない)です。
2. useStateで入力欄の状態を管理
const [input, setInput] = useState("");
- 入力欄の値はコンポーネントのstateで管理します(Controlled Component)。
input
が現在の入力値、setInput
が値を更新する関数。
3. フォーム送信イベントの制御
e.preventDefault();
- HTMLフォームはデフォルトで送信時にページをリロードします。これを防ぐために
preventDefault()
を呼びます。
10. UserList.tsx(結果表示)
コード全文
src/components/UserList.tsx
import React from "react";
type User = {
id: string;
login: string;
avatarUrl: string;
};
type Props = {
users: User[];
};
function UserList({ users }: Props) {
if (users.length === 0) return <p>ユーザーが見つかりません</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>
<img src={user.avatarUrl} alt={user.login} width={50} />
<span>{user.login}</span>
</li>
))}
</ul>
);
}
export default UserList;
解説ポイント
1. 配列のmapでリストを描画
users.map((user) => ( ... ))
- 配列の各要素を
<li>
に変換し、リストを生成します。 - Reactでは配列レンダリング時に必ず
key
属性を付与する必要があります。
2. ユーザーがいない場合の条件分岐
if (users.length === 0) return <p>ユーザーが見つかりません</p>;
- 検索結果が空配列の場合は、リストの代わりにメッセージを表示します。
11. 実行と動作確認
npm run dev
ブラウザで http://localhost:5173 を開き、
ユーザー名(例: testuser
)を検索するとGitHubユーザーが表示されます。


まとめ
ここまでで、GitHubユーザー検索アプリが完成しました。
最初に構成を見たときは、「コンポーネント分割」「GraphQL」「Apollo Client」…と聞き慣れない単語が多く、少し腰が引けた方もいるかもしれません。
でも、実際に手を動かしてみると、「あれ、思ったよりシンプルかも」と感じたはずです。

今回の作業で身についたポイントは、ざっくりこんなところです。
- 親子コンポーネント間でのデータ受け渡し(Props)の仕組み
- GraphQLクエリの基本構造と、必要なデータだけを取ってくる発想
- Apollo Clientを使ったAPI連携の流れ
- useLazyQueryで“必要なときだけ”データを取得する方法
個人的に、このアプリを作っていて改めて感じたのは、
「状態は親で管理し、見た目は子が担当する」というシンプルな設計ルールの強さです。
このおかげで、処理の流れがとても追いやすくなりましたし、コードの見通しも良くなりました。
もし余裕があれば、今回のアプリをベースに「ユーザーの詳細ページを追加する」「ページネーションを付ける」など、ちょっとした拡張にも挑戦してみてください!
コメントを残す