0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Node.jsとReact MUI+Viteで作るDeepSeekベースのChatGPTクローン開発入門

Last updated at Posted at 2025-07-05

はじめに

AIチャットボットの需要が高まる中、オープンな大規模言語モデル(LLM)を活用したアプリ開発が注目されています。DeepSeekは、OpenAI API互換の高性能LLMを提供し、コストパフォーマンスに優れた選択肢として人気です。本記事では、Node.jsとReact(MUI+Vite)を使い、DeepSeek APIと連携したChatGPTクローンをゼロから構築する全14章の実践ガイドをお届けします。各章で動作するコードを掲載し、コピペですぐに試せる内容です。

第1章:プロジェクトの全体像と要件整理

ChatGPTクローンを作るために、Node.jsでAPIサーバーを構築し、 React(MUI+Vite) でフロントエンドを作成します。バックエンドはDeepSeek APIと通信し、フロントエンドはチャットUIを実装します。APIキー管理やセキュリティ、非同期通信、ストリーミング対応なども考慮します。

第2章:開発環境の準備

まずNode.js(v16以上)とnpm、そしてViteを用意します。プロジェクトディレクトリを作成し、APIサーバーとフロントエンドを分離して管理します。

mkdir deepseek-chatgpt-clone
cd deepseek-chatgpt-clone
npm create vite@latest frontend -- --template react
mkdir backend
cd backend
npm init -y

第3章:DeepSeek APIキーの取得と管理

DeepSeek公式サイトでアカウント登録し、APIキーを取得します。backend/.envにAPIキーを安全に保存しましょう。

DEEPSEEK_API_KEY=取得したAPIキー

Node.jsでdotenvを使い、環境変数からAPIキーを読み込めるようにします。

第4章:Node.jsでAPIサーバーのセットアップ

Expressを使い、APIサーバーのベースを作ります。

cd backend
npm install express cors dotenv openai

backend/index.js:

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const OpenAI = require('openai');

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

const openai = new OpenAI({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: 'https://api.deepseek.com'
});

app.post('/api/chat', async (req, res) => {
  try {
    const { messages } = req.body;
    const completion = await openai.chat.completions.create({
      model: 'deepseek-chat',
      messages,
      stream: false
    });
    res.json({ content: completion.choices.message.content });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3001, () => console.log('API server running on http://localhost:3001'));

第5章:React+Vite+MUIのセットアップ

フロントエンドでMaterial UIを使い、モダンなUIを構築します。

cd ../frontend
npm install @mui/material @emotion/react @emotion/styled axios

frontend/src/main.jsx:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

第6章:チャットUIの基本レイアウト

MUIのコンポーネントでチャット画面の骨組みを作ります。

frontend/src/App.jsx:

import React, { useState } from 'react';
import { Container, Box, TextField, Button, List, ListItem, Paper } from '@mui/material';
import axios from 'axios';

function App() {
  const [input, setInput] = useState('');
  const [messages, setMessages] = useState([
    { role: 'system', content: 'あなたは親切なAIアシスタントです。' }
  ]);
  const [loading, setLoading] = useState(false);

  const handleSend = async () => {
    if (!input.trim()) return;
    const newMessages = [...messages, { role: 'user', content: input }];
    setMessages(newMessages);
    setInput('');
    setLoading(true);
    try {
      const res = await axios.post('http://localhost:3001/api/chat', { messages: newMessages });
      setMessages([...newMessages, { role: 'assistant', content: res.data.content }]);
    } catch (e) {
      setMessages([...newMessages, { role: 'assistant', content: 'エラーが発生しました。' }]);
    }
    setLoading(false);
  };

  return (
    <Container maxWidth="sm">
      <Box sx={{ mt: 4 }}>
        <Paper sx={{ p: 2, minHeight: 400 }}>
          <List>
            {messages.filter(m => m.role !== 'system').map((msg, i) => (
              <ListItem key={i} sx={{ justifyContent: msg.role === 'user' ? 'flex-end' : 'flex-start' }}>
                <Box sx={{ bgcolor: msg.role === 'user' ? 'primary.light' : 'grey.200', p: 1.5, borderRadius: 2 }}>
                  {msg.content}
                </Box>
              </ListItem>
            ))}
          </List>
        </Paper>
        <Box sx={{ display: 'flex', mt: 2 }}>
          <TextField
            fullWidth
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && handleSend()}
            disabled={loading}
            placeholder="メッセージを入力"
          />
          <Button onClick={handleSend} disabled={loading || !input.trim()} sx={{ ml: 1 }} variant="contained">
            送信
          </Button>
        </Box>
      </Box>
    </Container>
  );
}

export default App;

第7章:API通信のエラーハンドリング

API通信時のエラー表示や再送信ボタンを追加します。

// 上記App.jsxのcatch部分を拡張
catch (e) {
  setMessages([...newMessages, { role: 'assistant', content: 'エラーが発生しました。再送信してください。' }]);
}

第8章:チャット履歴のローカル保存

チャット履歴をlocalStorageに保存し、リロード後も会話を復元します。

import { useEffect } from 'react';

useEffect(() => {
  const saved = localStorage.getItem('chat-messages');
  if (saved) setMessages(JSON.parse(saved));
}, []);

useEffect(() => {
  localStorage.setItem('chat-messages', JSON.stringify(messages));
}, [messages]);

第9章:ユーザーごとのセッション管理

ユーザーごとにセッションIDを発行し、APIリクエストに含めることで個別の会話を維持します。

const [sessionId] = useState(() => {
  return localStorage.getItem('session-id') || (() => {
    const id = Math.random().toString(36).slice(2);
    localStorage.setItem('session-id', id);
    return id;
  })();
});

// axios.post時にsessionIdも送信
await axios.post('http://localhost:3001/api/chat', { messages: newMessages, sessionId });

第10章:ストリーミング応答への対応

DeepSeek APIのstream機能を活用し、サーバー側でストリーミングレスポンスを処理します。
バックエンド(Express)でSSEやWebSocketの導入も可能ですが、まずは通常レスポンスで構築し、後から拡張します。

第11章:MUIテーマによるダークモード対応

MUIのThemeProviderを使い、ダークモード切り替えを実装します。

import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';

const [dark, setDark] = useState(false);
const theme = createTheme({ palette: { mode: dark ? 'dark' : 'light' } });

<ThemeProvider theme={theme}>
  <CssBaseline />
  {/* ...App内容... */}
  <Button onClick={() => setDark(!dark)}>ダークモード切替</Button>
</ThemeProvider>

第12章:APIキーのフロント非公開化とセキュリティ

APIキーは必ずバックエンドのみで管理し、フロントエンドには絶対に公開しません。
CORSやRate Limit、バリデーションも必ず実装しましょう。

第13章:デプロイ準備とビルド

Viteでフロントエンドをビルドし、バックエンドと統合します。

cd frontend
npm run build
# buildフォルダをbackend/publicにコピー
cp -r dist ../backend/public

Expressで静的ファイル配信を追加:

app.use(express.static('public'));
app.get('*', (req, res) => {
  res.sendFile(__dirname + '/public/index.html');
});

第14章:本番運用・まとめ

VercelやRender、VPSなどにNode.jsサーバーをデプロイし、APIキーの安全な管理やHTTPS化も行いましょう。
DeepSeekはコスト効率に優れ、OpenAI互換で扱いやすいため、ChatGPTクローン開発の強力な選択肢です。
本記事の手順を参考に、独自のAIチャットサービスを構築・拡張してみてください。

おわりに
DeepSeek・Node.js・React(MUI+Vite)の組み合わせで、現代的かつ実用的なAIチャットアプリが手軽に開発できます。ぜひ自分だけのAIサービスを作り、次世代のユーザー体験を実現しましょう。


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?