Node.js、Express、GraphQL、htmxを使ったチャットアプリの作成

## このアプリについて

このチュートリアルでは、Node.jsとExpressをバックエンドに、GraphQLをデータ取得に、htmxをフロントエンドに使用したリアルタイムチャットアプリを作成します。

また、他のユーザーがチャットに参加しているかどうか、メッセージを読んだかどうかも確認できる機能を追加します。

### 必要なツール

- Node.js
- npm
- Express
- Apollo Server (GraphQL)
- htmx

### 各技術の説明

**Node.js**: JavaScriptランタイムで、サーバーサイドのスクリプトを実行するために使用されます。

**Express**: Node.js上で動作するウェブアプリケーションフレームワークで、APIの作成やルーティングに便利です。

**GraphQL**: データの取得と操作を行うためのクエリ言語で、クライアントが必要なデータだけを取得できます。

**htmx**: HTML属性を使って、サーバーからのデータを直接DOMに反映させることができるライブラリです。

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

### Node.jsとExpressのインストール

```bash
mkdir chat-app
cd chat-app
npm init -y
npm install express apollo-server-express graphql htmx
```

### サーバーの設定

`server.js`を作成し、ExpressとApollo Serverを設定します。

```javascript
const express = require('express');
const { ApolloServer, gql, PubSub } = require('apollo-server-express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

const pubsub = new PubSub();
const MESSAGE_ADDED = 'MESSAGE_ADDED';

const typeDefs = gql`
  type Message {
    id: ID!
    content: String!
    user: String!
    readBy: [String!]
  }
  type Query {
    messages: [Message]
  }
  type Mutation {
    addMessage(content: String!, user: String!): Message
    markAsRead(messageId: ID!, user: String!): Message
  }
  type Subscription {
    messageAdded: Message
  }
`;

let messages = [];

const resolvers = {
  Query: {
    messages: () => messages,
  },
  Mutation: {
    addMessage: (_, { content, user }) => {
      const message = { id: messages.length + 1, content, user, readBy: [] };
      messages.push(message);
      pubsub.publish(MESSAGE_ADDED, { messageAdded: message });
      return message;
    },
    markAsRead: (_, { messageId, user }) => {
      const message = messages.find(msg => msg.id == messageId);
      if (message && !message.readBy.includes(user)) {
        message.readBy.push(user);
      }
      return message;
    }
  },
  Subscription: {
    messageAdded: {
      subscribe: () => pubsub.asyncIterator([MESSAGE_ADDED])
    }
  }
};

const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });

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

### フロントエンドの設定

`index.html`を作成し、htmxを使用してチャットインターフェースを構築します。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>チャットアプリ</title>
  <script src="https://unpkg.com/htmx.org@1.9.10"></script>
  <script src="https://unpkg.com/htmx.org/dist/ext/ws.js"></script>
</head>
<body>
  <div id="chat">
    <div hx-get="/messages" hx-trigger="load" hx-swap="innerHTML"></div>
    <form hx-post="/addMessage" hx-trigger="submit" hx-swap="beforeend">
      <input type="text" name="content" required>
      <input type="hidden" name="user" value="ユーザー名">
      <button type="submit">送信</button>
    </form>
  </div>
  <div id="read-status">
    <div hx-get="/readStatus" hx-trigger="load" hx-swap="innerHTML"></div>
  </div>
</body>
</html>
```

### メッセージの取得と追加

サーバー側でメッセージの取得と追加をハンドリングするエンドポイントを設定します。

```javascript
app.get('/messages', (req, res) => {
  res.send(messages.map(msg => `<div>${msg.user}: ${msg.content}</div>`).join(''));
});

app.post('/addMessage', (req, res) => {
  const { content, user } = req.body;
  const message = { id: messages.length + 1, content, user, readBy: [] };
  messages.push(message);
  res.send(`<div>${user}: ${content}</div>`);
});
```

### メッセージの既読状態の管理

メッセージが読まれたかどうかを管理するためのエンドポイントを追加します。

```javascript
app.post('/markAsRead', (req, res) => {
  const { messageId, user } = req.body;
  const message = messages.find(msg => msg.id == messageId);
  if (message && !message.readBy.includes(user)) {
    message.readBy.push(user);
  }
  res.send(message);
});

app.get('/readStatus', (req, res) => {
  res.send(messages.map(msg => `<div>${msg.user}: ${msg.content} - Read by: ${msg.readBy.join(', ')}</div>`).join(''));
});
```

## まとめ

このチュートリアルでは、Node.jsとExpressをバックエンドに、GraphQLをデータ取得に、htmxをフロントエンドに使用したチャットアプリを作成しました。

また、他のユーザーがチャットに参加しているかどうか、メッセージを読んだかどうかも確認できる機能を追加しました。

詳細なコードと設定により、リアルタイムでメッセージを送受信できるシンプルなチャットアプリを構築しました。