Node.js、Next.js、GraphQLでマッチングアプリを作ろう！



## どんどん作りましょう！

こんにちは、皆さん！今回は、最新のWeb技術を使って、シンプルながら機能的なマッチングアプリを作成する方法をご紹介します。Node.js、Next.js、そしてGraphQLを使用して、モダンで効率的なアプリケーションを構築していきましょう。

### 目次
1. プロジェクトのセットアップ
2. バックエンドの構築：Node.jsとGraphQL
3. フロントエンドの開発：Next.jsの導入
4. ユーザー認証の実装
5. マッチング機能の開発
6. リアルタイム通知の追加
7. デプロイメントと最適化

それでは、各章で詳しく見ていきましょう！

### 1. プロジェクトのセットアップ

まずは、新しいプロジェクトを作成し、必要な依存関係をインストールしましょう。

```bash
mkdir matching-app
cd matching-app
npm init -y
npm install express apollo-server-express graphql mongoose next react react-dom
npm install --save-dev nodemon @babel/core @babel/preset-env
```

`package.json`ファイルに以下のスクリプトを追加します：

```json
{
  "scripts": {
    "dev": "nodemon server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  }
}
```

### 2. バックエンドの構築：Node.jsとGraphQL

バックエンドの構築から始めましょう。`server.js`ファイルを作成し、Express.jsとApollo Serverをセットアップします。

```javascript
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const mongoose = require('mongoose');

// GraphQLスキーマの定義
const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    age: Int!
    interests: [String!]!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }

  type Mutation {
    createUser(name: String!, age: Int!, interests: [String!]!): User!
  }
`;

// リゾルバーの実装
const resolvers = {
  Query: {
    users: () => users,
    user: (_, { id }) => users.find(user => user.id === id),
  },
  Mutation: {
    createUser: (_, { name, age, interests }) => {
      const newUser = { id: String(users.length + 1), name, age, interests };
      users.push(newUser);
      return newUser;
    },
  },
};

// ダミーデータ
let users = [
  { id: '1', name: '田中太郎', age: 28, interests: ['スポーツ', '読書'] },
  { id: '2', name: '佐藤花子', age: 24, interests: ['音楽', '旅行'] },
];

async function startServer() {
  const app = express();
  const server = new ApolloServer({ typeDefs, resolvers });

  await server.start();
  server.applyMiddleware({ app });

  // MongoDBの接続（実際の接続文字列に置き換えてください）
  await mongoose.connect('mongodb://localhost:27017/matching-app', { useNewUrlParser: true, useUnifiedTopology: true });

  app.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
  );
}

startServer();
```

### 3. フロントエンドの開発：Next.jsの導入

次に、Next.jsを使用してフロントエンドを構築します。`pages`ディレクトリを作成し、その中に`index.js`ファイルを作成します。

```jsx
// pages/index.js
import { useState, useEffect } from 'react';

export default function Home() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetchUsers();
  }, []);

  async function fetchUsers() {
    const response = await fetch('/api/graphql', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        query: `
          query {
            users {
              id
              name
              age
              interests
            }
          }
        `
      }),
    });
    const { data } = await response.json();
    setUsers(data.users);
  }

  return (
    <div>
      <h1>マッチングアプリ</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name} ({user.age}歳) - 興味: {user.interests.join(', ')}
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### 4. ユーザー認証の実装

ユーザー認証を実装するために、新しいGraphQLミューテーションとクエリを追加します。`server.js`ファイルを更新します。

```javascript
// server.js (追加部分)

const typeDefs = gql`
  // ... 既存のタイプ定義 ...

  type AuthPayload {
    token: String!
    user: User!
  }

  type Mutation {
    // ... 既存のミューテーション ...
    signup(name: String!, email: String!, password: String!): AuthPayload!
    login(email: String!, password: String!): AuthPayload!
  }
`;

const resolvers = {
  // ... 既存のリゾルバー ...
  Mutation: {
    // ... 既存のミューテーション ...
    signup: async (_, { name, email, password }) => {
      // ここでユーザーを作成し、トークンを生成する実装を行います
      // この例では簡略化のため、詳細は省略します
      const user = { id: String(users.length + 1), name, email };
      const token = 'dummy_token';
      return { user, token };
    },
    login: async (_, { email, password }) => {
      // ここで認証を行い、トークンを生成する実装を行います
      // この例では簡略化のため、詳細は省略します
      const user = users.find(u => u.email === email);
      if (!user) throw new Error('ユーザーが見つかりません');
      const token = 'dummy_token';
      return { user, token };
    },
  },
};
```

### 5. マッチング機能の開発

マッチング機能を実装するために、新しいGraphQLミューテーションを追加します。

```javascript
// server.js (追加部分)

const typeDefs = gql`
  // ... 既存のタイプ定義 ...

  type Match {
    id: ID!
    users: [User!]!
    createdAt: String!
  }

  type Mutation {
    // ... 既存のミューテーション ...
    createMatch(userId1: ID!, userId2: ID!): Match!
  }
`;

const resolvers = {
  // ... 既存のリゾルバー ...
  Mutation: {
    // ... 既存のミューテーション ...
    createMatch: (_, { userId1, userId2 }) => {
      const user1 = users.find(u => u.id === userId1);
      const user2 = users.find(u => u.id === userId2);
      if (!user1 || !user2) throw new Error('ユーザーが見つかりません');
      const match = {
        id: String(matches.length + 1),
        users: [user1, user2],
        createdAt: new Date().toISOString(),
      };
      matches.push(match);
      return match;
    },
  },
};

let matches = [];
```

### 6. リアルタイム通知の追加

リアルタイム通知を実装するために、GraphQL Subscriptionsを使用します。`server.js`ファイルを更新し、新しい依存関係をインストールします。

```bash
npm install graphql-subscriptions subscriptions-transport-ws
```

```javascript
// server.js (追加部分)

const { PubSub } = require('graphql-subscriptions');
const { createServer } = require('http');
const { execute, subscribe } = require('graphql');
const { SubscriptionServer } = require('subscriptions-transport-ws');
const { makeExecutableSchema } = require('@graphql-tools/schema');

const pubsub = new PubSub();

const typeDefs = gql`
  // ... 既存のタイプ定義 ...

  type Subscription {
    newMatch: Match!
  }
`;

const resolvers = {
  // ... 既存のリゾルバー ...
  Subscription: {
    newMatch: {
      subscribe: () => pubsub.asyncIterator(['NEW_MATCH']),
    },
  },
  Mutation: {
    // ... 既存のミューテーション ...
    createMatch: (_, { userId1, userId2 }) => {
      // ... 既存のコード ...
      pubsub.publish('NEW_MATCH', { newMatch: match });
      return match;
    },
  },
};

async function startServer() {
  const app = express();
  const httpServer = createServer(app);

  const schema = makeExecutableSchema({ typeDefs, resolvers });

  const server = new ApolloServer({
    schema,
    plugins: [{
      async serverWillStart() {
        return {
          async drainServer() {
            subscriptionServer.close();
          }
        };
      }
    }],
  });

  await server.start();
  server.applyMiddleware({ app });

  const subscriptionServer = SubscriptionServer.create(
    { schema, execute, subscribe },
    { server: httpServer, path: server.graphqlPath }
  );

  // ... MongoDBの接続 ...

  httpServer.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
  );
}

startServer();
```

### 7. デプロイメントと最適化

最後に、アプリケーションのデプロイメントと最適化について考えましょう。以下は主な手順です：

1. 環境変数の使用：セキュリティを向上させるため、機密情報を環境変数として設定します。

2. エラーハンドリングの改善：適切なエラーメッセージを表示し、ユーザー体験を向上させます。

3. キャッシング：Apollo Clientを使用してクライアント側でのキャッシングを実装し、パフォーマンスを向上させます。

4. コード分割：Next.jsの動的インポート機能を使用して、コードを分割し、初期ロード時間を短縮します。

5. SEO最適化：Next.jsのSSRとSSG機能を活用して、検索エンジン最適化を行います。

これらの最適化を行うことで、アプリケーションのパフォーマンスとユーザー体験を大幅に向上させることができます。

## まとめ

以上で、Node.js、Next.js、GraphQLを使用したマッチングアプリの基本的な実装が完了しました。この記事では、バックエンドの構築からフロントエンドの開発、認証の実装、マッチング機能の追加、そしてリアルタイム通知の実装まで、幅広いトピックをカバーしました。

このプロジェクトをベースに、さらに機能を追加したり、UIを改善したりすることで、より高度なマッチングアプリを開発することができます。最新のWeb技術を学ぶ良い機会になれば幸いです。

開発を楽しんでください！何か質問があれば、コメント欄でお気軽にお尋ねください。