はじめに
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サービスを作り、次世代のユーザー体験を実現しましょう。