はじめに
以前作成した ログインしたユーザーがTODOリストを作成できるようにする をNext.jsに移行します。
next.jsとは
Next.jsは、Facebookが開発したReactベースのフレームワークです。サーバーサイドレンダリング(SSR)や静的サイト生成(SSG)を容易に実装できるよう設計されており、SEO対策やパフォーマンスの最適化にも効果的です。
準備
Node.js と npm のインストール
node -v
npm -v
作業ディレクトリに移動します(例:workspace)
cd ~/workspace
Reactプロジェクトの作成
npx create-react-app app todo
cd app todo
ページコンポーネントの作成
HTMLファイルをNext.jsのページコンポーネント(.jsxファイル)に変換します。pagesディレクトリ内に以下のファイルを作成します
作成したフォルダは以下です
📦pages
┣ 📜contact.jsx
┣ 📜login.jsx
┣ 📜profile.jsx
┗ 📜todo.jsx
┗ 📜signup.jsx
JSXへの変換
HTMLをJSXに変換する際、以下の変更が必要です
- class属性をclassNameに変更します。
- 自己閉じタグにはスラッシュを追加します(例:< img />)。
- 属性値がJavaScriptの式である場合は、{}で囲みます(例:< input type="text" value={username} />)。
login.jsxファイル
import '../styles/styles.css'
import axios from 'axios'
import { useState } from 'react'
import { useRouter } from 'next/router'
import toastr from 'toastr'
import 'toastr/build/toastr.min.css'
import Link from 'next/link'
export default function Login() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const router = useRouter()
const handleLogin = async (e) => {
e.preventDefault()
try {
console.log(email)
console.log(password)
const response = await axios.post(
'http://localhost:4000/api/users/login',
{ email, password },
)
console.log(response)
toastr.success(response.data.message)
localStorage.setItem('userId', response.data.userId)
router.push('/profile')
} catch (error) {
toastr.error('メールアドレスまたはパスワードが正しくありません。')
}
}
return (
<div className="container">
<h2>ログイン</h2>
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレス"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="パスワード"
required
/>
<button type="submit">ログイン</button>
</form>
<p>
アカウントをお持ちでない方は
<Link href="/signup">新規アカウント作成</Link>
</p>
</div>
)
}
signup.jsxファイル
// src/pages/signup.jsx
import '../styles/styles.css'
import { useState } from 'react'
import axios from 'axios'
import toastr from 'toastr'
import 'toastr/build/toastr.min.css'
export default function Signup() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [username, setUsername] = useState('')
const [userId, setUserId] = useState('')
const [userIcon, setUserIcon] = useState(null)
const handleSubmit = async (e) => {
e.preventDefault()
const formData = new FormData()
formData.append('image', userIcon)
try {
const imageResponse = await axios.post(
`https://api.imgbb.com/1/upload?key=c0f0b6237007dc47943f085d13b621c1`,
formData,
)
console.log(imageResponse)
console.log(imageResponse.data.status)
if (imageResponse.data.status === 200) {
const imageUrl = imageResponse.data.data.url
console.log(imageUrl)
// ユーザー情報と画像URLをサーバーに送信
const userResponse = await axios.post(
'http://localhost:4000/api/users/register',
{
email,
password,
name: username,
userId,
userIcon: imageUrl,
},
)
console.log(userResponse)
console.log(userResponse.status)
if (userResponse.status === 201) {
toastr.success('アカウントが正常に作成されました。')
window.location.href = '/login'
} else {
throw new Error('Failed to create user')
}
} else {
throw new Error('Failed to upload image')
}
} catch (error) {
console.error('Error during account creation:', error)
toastr.error('アカウントの作成に失敗しました。')
}
}
return (
<div className="container">
<h2>新規アカウント作成</h2>
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレス"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="パスワード"
required
/>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="ユーザー名"
required
/>
<input
type="text"
value={userId}
onChange={(e) => setUserId(e.target.value)}
placeholder="ユーザーID"
required
/>
<input
type="file"
onChange={(e) => setUserIcon(e.target.files[0])}
accept="image/*"
/>
<button type="submit">アカウント作成</button>
</form>
</div>
)
}
profile.jsxファイル
// src/pages/profile.jsx
//モジュールのインストール
import '../styles/styles.css'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import Image from 'next/image'
import axios from 'axios'
import Link from 'next/link'
export default function Profile() {
//状態の初期化
const [username, setUsername] = useState('')//ユーザー名を管理する状態
const [userIcon, setUserIcon] = useState('')//ユーザーアイコンを管理する状態
const router = useRouter()//Next.jsのルーターを初期化
useEffect(() => {
const fetchProfile = async () => {//ページが読み込まれたらfetchProfile関数が実行される ストレージからユーザーIDを取得
const userId = localStorage.getItem('userId')//ローカルストレージから保存されたユーザーIDを取得
if (!userId) {
router.push('/login')//ユーザーIDがない場合ログインページにリダイレクト
} else {
try {
const response = await axios.get(
`http://localhost:4000/api/users/${userId}`,//APIからユーザー情報を取得
)
const { name, userIcon } = response.data//取得したデータを分割代入
setUsername(name)//取得したemailをsetUsernameにセットする
setUserIcon(userIcon)//取得したuserIconをsetUserIconにセットする
} catch (error) {
console.error('プロフィール取得エラー:', error)// エラー時の処理
router.push('/login')// ログインページにリダイレクト
}
}
}
fetchProfile()// ページが読み込まれたときに実行
}, [router])
return (
<div className="container">
<h2>
ようこそ、<span>{username}</span>さん
</h2>
{userIcon && (
<Image src={userIcon} alt="ユーザーアイコン" width={200} height={200} />
)}
<p>
<Link href="/todo">TODOリストを管理</Link>
</p>
<p>
<Link href="/contact">お問い合わせ</Link>
</p>
</div>
)
}
todo.jsxファイル
// src/pages/todo.jsx
import '../styles/styles.css'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import axios from 'axios'
export default function Todo() {
const [tasks, setTasks] = useState([])
const [taskName, setTaskName] = useState('')
const [taskDescription, setTaskDescription] = useState('')
const [taskDeadline, setTaskDeadline] = useState('')
const [userId, setUserId] = useState('')
const router = useRouter()
useEffect(() => {
fetchTasks()
}, [])
useEffect(() => {
const fetchProfile = async () => {
console.log(`fetchProfile()`)
const userId = localStorage.getItem('userId')
if (!userId) {
router.push('/login')
} else {
setUserId(userId)
}
}
fetchProfile()
console.log(`userId = `, userId)
}, [router])
//タスク一覧を取得
const fetchTasks = async () => {
//関数の宣言 タスクをサーバーから取得する処理
console.log(`fetchTasks()`)
try {
const response = await axios.get('http://localhost:4000/tasks') ///サーバーにリクエストを送信
console.log(response)
setTasks(response.data) //取得したデータをtasksに保存
} catch (error) {
console.error('Failed to fetch tasks:', error) //サーバーにリクエストを送るときにエラーが発生した場合エラーメッセージを表示
}
}
//タスクを追加
const handleSubmit = async () => {
//新しいタスクを生成してサーバーに送信する処理
console.log(`handleSubmit()`)
console.log(`userId = `, userId)
//ユーザーIDの確認
if (!userId) {
router.push('/login')
} else {
const newTask = {
userId: Number(userId),
title: taskName,
description: taskDescription,
deadline: taskDeadline,
}
console.log(`newTask =`, newTask)
try {
const response = await axios.post(
'http://localhost:4000/tasks',
newTask,
)
console.log(`response =`, response)
setTasks([...tasks, response.data])
setTaskName('')
setTaskDescription('')
setTaskDeadline('')
} catch (error) {
console.error('Failed to add task:', error)
}
}
}
//タスクの削除
const handleDelete = async (taskId) => {
try {
await axios.delete(`http://localhost:4000/tasks/${taskId}`)
const updatedTasks = tasks.filter((task) => task.id !== taskId)
setTasks(updatedTasks)
} catch (error) {
console.error('Failed to delete task:', error)
}
}
console.log(handleDelete)
//コンポーネントの構成
return (
<div className="container">
<h2>TODOリスト</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
value={taskName}
onChange={(e) => setTaskName(e.target.value)}
placeholder="タスク名"
required
/>
<textarea
value={taskDescription}
onChange={(e) => setTaskDescription(e.target.value)}
placeholder="タスク説明"
required
></textarea>
<input
type="date"
value={taskDeadline}
onChange={(e) => setTaskDeadline(e.target.value)}
required
/>
<button type="submit">タスク追加</button>
</form>
<button onClick={() => router.push('/profile')} className="back-button">
プロフィールに戻る
</button>
<div className="todo-list">
{tasks.map((task, index) => (
<div key={task.id || index}>
<p>タスク名: {task.name}</p>
<p>説明: {task.description}</p>
<p>期限: {task.deadline}</p>
<button onClick={() => handleDelete(task.id)}>削除</button>
</div>
))}
</div>
</div>
)
}
contact.jsxファイル
// src/pages/contact.jsx
import '../styles/styles.css'
import axios from 'axios'
// src/pages/Contact.jsx
import { useState } from 'react';
export default function Contact() {
const [formData, setFormData] = useState({
email: '',
username: '',
query: '',
});
const [isSubmitting, setIsSubmitting] = useState(false); // 送信中の状態を管理
// フォームの入力が変更されたときの処理
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
// フォーム送信処理
const handleSubmit = async (e) => {
e.preventDefault(); // デフォルトのフォーム送信を防止
setIsSubmitting(true); // 送信中の状態に設定
try {
const response = await fetch('https://formspree.io/f/xpwavooz', {
method: 'POST',
body: new URLSearchParams(formData), // フォームデータを送信
headers: {
Accept: 'application/json',
},
});
const data = await response.json();
if (response.ok) {
alert('お問い合わせありがとうございます!');
setFormData({ email: '', username: '', query: '' }); // フォームをリセット
} else {
alert('送信に失敗しました。');
}
} catch (error) {
alert('エラーが発生しました。');
} finally {
setIsSubmitting(false); // 送信ボタンを再び有効化
}
};
return (
<div className="container">
<h2>お問い合わせ</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="email">メールアドレス:</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
<br />
<label htmlFor="username">ユーザー名:</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
required
/>
<br />
<label htmlFor="query">問い合わせ内容:</label>
<textarea
id="query"
name="query"
value={formData.query}
onChange={handleChange}
required
rows="10"
cols="50"
></textarea>
<br />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '送信中...' : '送信'}
</button>
</form>
</div>
);
}
スタイルの統合
CSSファイルをNext.jsプロジェクトに統合します。src/stylesディレクトリにstyles.cssファイルを保存し、各ページから以下のようにインポートします
// src/pages/login.jsx
import '../styles/styles.css';
styles.cssファイル
/* 基本設定 */
body {
font-family: 'Poppins', sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* コンテナのスタイル */
.container {
width: 100%;
max-width: 350px;
padding: 30px;
background-color: white;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
text-align: center;
}
/* 見出しスタイル */
h2 {
margin-bottom: 20px;
font-size: 1.5rem;
color: #333;
font-weight: 600;
}
/* 入力フィールドのスタイル */
input[type='email'],
input[type='password'],
input[type='text'],
input[type='file'] {
width: 80%;
max-width: 280px;
padding: 12px 15px;
margin: 10px 0;
border: 2px solid #ddd;
border-radius: 8px;
background-color: #f9f9f9;
font-size: 1rem;
transition:
border 0.3s,
background-color 0.3s;
}
/* フォーカス時のスタイル */
input[type='email']:focus,
input[type='password']:focus,
input[type='text']:focus {
border-color: #b654c5;
background-color: white;
outline: none;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}
/* ボタンのスタイル */
button {
width: 80%;
max-width: 280px;
padding: 12px;
background-color: #b654c5;
color: white;
font-size: 1rem;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
transition:
background-color 0.3s,
box-shadow 0.3s;
}
/* ボタンホバー時のエフェクト */
button:hover {
background-color: #b654c5;
box-shadow: 0 4px 12px rgba(0, 91, 179, 0.2);
}
/* リンクのスタイル */
p a {
color: #b654c5;
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
}
p a:hover {
color: #b654c5;
}
/* ユーザーアイコンのスタイル */
#displayIcon {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
margin-top: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
/* レスポンシブ設定 */
@media (max-width: 768px) {
.container {
width: 90%;
}
}
/* TODOリストのスタイル調整 */
.todo-list {
width: 100%;
max-width: 350px;
margin-top: 20px;
padding: 10px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.todo-list div {
padding: 10px;
margin: 5px 0;
border-bottom: 1px solid #eee;
}
.todo-list div:last-child {
border-bottom: none;
}
.todo-list p {
margin: 5px 0;
color: #333;
}
button {
margin-top: 10px;
}
APIルートの設定(準備)
開発環境の準備
Node.jsのインストール
Node.jsがまだインストールされていない場合は、Node.js公式サイトからインストーラをダウンロードしてインストールしてください。
Node.js公式サイト
新しいプロジェクトの作成
Express.jsとその他の依存関係を含む新しいプロジェクトを設定します。データベースとの接続にはMySQLドライバを使用し、環境変数の管理にはdotenvを導入します。
mkdir my-api
cd my-api
npm init -y
npm install express mysql dotenv
アプリケーションの実行
アプリケーションを設定し、以下のようにサーバーを起動します
node index.js
これで、基本的なAPIのセットアップが完了し、Express.jsとMySQLを使用して開発を進めることができます。
APIルート
Next.jsのAPIルートを利用して、フォームの送信や外部APIとの通信をサーバーサイドで処理します。pages/apiディレクトリ内に必要なAPIルートファイルを作成します。
ユーザー保存API
users.jsファイル
const express = require('express')
const router = express.Router()
const { PrismaClient } = require('@prisma/client')
const bcrypt = require('bcryptjs') // パスワードを暗号化するためのライブラリ
const prisma = new PrismaClient()
// 新規アカウント作成
//クライアントが POSTリクエストで /register にメールアドレス、名前、パスワードを送信。
//サーバーは、パスワードをハッシュ化し、データベースに新しいユーザーを登録。
//登録が成功したら、201(新規作成)とユーザーIDを返す。
//もしエラーが発生したら、500エラーを返す。
router.post('/register', async (req, res) => {//新しいユーザーが登録するAPIを定義する
const { email, name, password, userIcon } = req.body//クライアントからメールアドレス、名前、パスワードを取得
console.log('email=',email)
console.log('name=',name)
console.log('password=',password)
console.log('userIcon=',userIcon)
console.log('req.body=',req.body)
try {//例外が発生する可能性のある処理 try~catch文
// パスワードのハッシュ化
const salt = await bcrypt.genSalt(10) // パスワードのハッシュ化に必要な「塩」を生成
const hashedPassword = await bcrypt.hash(password, salt) //塩を使って、パスワードをハッシュ化
// 新しいユーザーをデータベースに保存
const newUser = await prisma.user.create({//データベースに新しいユーザーを登録
data: {
email: email,//メールを保存
name: name,//名前を保存
password: hashedPassword, // ハッシュ化されたパスワードを保存
userIcon: userIcon, // アイコンURLを保存//追加部分
},
})
//ユーザー登録成功時のレスポンス
res.status(201).json({//201(新規作成)を返す
message: 'ユーザー登録に成功しました',
userId: newUser.id, // 新しく作成されたユーザーのIDを返す
})
} catch (error) {//例外が発生した場合の処理 try~catch文
console.error('ユーザー登録に失敗しました:', error)
res.status(500).json({ message: 'ユーザー登録に失敗しました' })//500(サーバー内部エラー)を返す
}
})
// 全ユーザーの取得
//クライアントが GETリクエストで / にアクセス。
//サーバーは、データベースからすべてのユーザー情報を取得。
//取得が成功したら、**200(成功)**とともにユーザー情報を返す。
//もしエラーが発生したら、500エラーを返す。
router.get('/', async (req, res) => {//全てのユーザー情報を取得するAPIを定義する
try {//例外が発生する可能性のある処理 try~catch文
const users = await prisma.user.findMany()//データベースに保存されている全てのユーザーを取得
res.status(200).json(users)//200(成功)を返す、取得したユーザー情報をJSON形式で返す
} catch (error) {//例外が発生した場合の処理 try~catch文
console.error('ユーザー取得に失敗しました:', error)
res.status(500).json({ message: 'ユーザー取得に失敗しました' })//500(サーバー内部エラー)を返しメッセージを表示
}
})
// ユーザーログイン
//まとめ(処理の流れ)
//ユーザーがメールアドレスとパスワードをPOSTリクエストで送信します。
//サーバーはデータベースでそのユーザーを探します。
//ユーザーが見つかり、かつパスワードが正しい場合は、ログイン成功のメッセージを返します。
//ユーザーが見つからない、またはパスワードが間違っていた場合は、認証エラーメッセージを返します。
//エラーが発生した場合は、サーバー内部エラーのメッセージを返します。
router.post('/login', async (req, res) => {//ユーザーがログイン情報を送信するリクエストを受け付けます
const { email, password } = req.body//ユーザーが入力したメールアドレスとパスワードを取り出す
try {//例外が発生する可能性のある処理 try~catch文
const user = await prisma.user.findUnique({
where: { email: email },//データベースからメールアドレスが一致するユーザーを検索する
})
if (user && (await bcrypt.compare(password, user.password))) {//ユーザーが入力したパスワードとデータベースに保存されているハッシュ化されたパスワードを比較
res.status(200).json({//200(成功)を返す
message: 'ログイン成功',
userId: user.id,
userIcon: user.userIcon, // アイコンURLを追加
})
} else {//ログイン失敗時の処理
res.status(401).json({ message: '認証情報が無効です' })//ユーザーが見つからないパスワードが間違っている場合401(認証失敗)を返す
}
} catch (error) {//例外が発生した場合の処理 try~catch文
console.error('ログインに失敗しました:', error)//エラーが起きた時コンソールにエラーを表示する
res.status(500).json({ message: 'ログインに失敗しました' })//500(サーバー内部エラー)を返しメッセージを表示
}
})
module.exports = router
// 特定のユーザーをIDで取得する
//特定のIDに対応するユーザーをデータベースから探し出して返すAPIを作っています。
//ユーザーが見つかればその情報を返し、見つからなければ「404 エラー」を返します。
//エラーが発生した場合には、「500 エラー」とエラーメッセージを返します。
router.get('/:id', async (req, res) => {///api/users/:id にアクセスしたときユーザーの情報をデータベースから取得し返す
const userId = parseInt(req.params.id) // IDを取得する 文字列を数値に変換
try { //例外が発生する可能性のある処理
const user = await prisma.user.findUnique({//データベースから特定のユーザーを探す
where: { id: userId },//特定のIdに一致するユーザーを探す
})
if (user) {//データベースからユーザーが見つかった場合の処理
res.status(200).json(user)//200(成功)を返す
} else {//データベースからユーザーが見つからなかった場合の処理
res.status(404).json({ message: 'ユーザーが見つかりませんでした' })//404(見つからない)を返す
}
} catch (error) { //例外が発生した場合の処理 try~catch文
console.error('ユーザー取得に失敗しました:', error)//エラーメッセージをコンソールに表示
res.status(500).json({ message: 'ユーザー取得に失敗しました' })//500(サーバー内部エラー)を返す
}
})
module.exports = router
タスク保存API
tasks.jsファイル
const express = require('express') //ライブラリをインポート
const router = express.Router() //リクエストの受け口
const { PrismaClient } = require('@prisma/client') //Prisma クライアントをインポート
const prisma = new PrismaClient() //PrismaClient のインスタンスを作成
// タスク保存のルート
router.post('/', async (req, res) => {
console.log(`req.body = `, req.body)
const { userId, title, description, deadline } = req.body //req.bodyから{}の中身を受け取る
try {
// ユーザーの存在確認
const user = await prisma.user.findUnique({
where: { id: userId },
}) //指定されたuserIdが存在しているか確認
console.log(`user = `, user)
if (!user) {
return res.status(404).json({ message: 'ユーザーが見つかりませんでした' })
} //指定されたuserIdが存在しない場合404エラーを返す
const newTask = await prisma.task.create({
//ユーザーが存在する場合は新しいタスクをデータベースに保存
data: {
userId: userId,
title: title,
description: description,
deadline: new Date(deadline),
},
})
console.log(`newTask = `, newTask)
res.status(201).json({
//タスクが正常に作成されたら201ステータスで作成したタスクの情報を返す
message: 'タスクが正常に作成されました',
taskId: newTask.id,
title: newTask.title,
description: newTask.description,
deadline: newTask.deadline,
})
} catch (error) {
console.error('タスク作成に失敗しました:', error)
res.status(500).json({ message: 'タスク作成に失敗しました' })
}
})
// 特定のユーザーのすべてのタスクを取得するルート
router.get('/user/:userId', async (req, res) => {
const userId = parseInt(req.params.userId)
try {
// ユーザーの存在確認
const user = await prisma.user.findUnique({
where: { id: userId },
})
if (!user) {
return res.status(404).json({ message: 'ユーザーが見つかりませんでした' })
}
const tasks = await prisma.task.findMany({
where: {
userId: userId,
},
})
res.status(200).json(tasks)
} catch (error) {
console.error('タスクの取得に失敗しました:', error)
res.status(500).json({ message: 'タスクの取得に失敗しました' })
}
})
// タスクをIDによって取得するルート
router.get('/:id', async (req, res) => {
const taskId = parseInt(req.params.id) //文字列を整数に変換
try {
const task = await prisma.task.findUnique({
where: {
id: taskId,
},
})
if (task) {
res.status(200).json(task)
} else {
res.status(404).json({ message: 'タスクが見つかりませんでした' }) //指定されたuserIdがデータベースに存在しない場合404エラーを返す
}
} catch (error) {
console.error('タスクの取得に失敗しました:', error)
res.status(500).json({ message: 'タスクの取得に失敗しました' })
}
})
// タスクをIDによって削除するルート
router.delete('/:id', async (req, res) => {
const taskId = parseInt(req.params.id) //文字列を整数に変換
try {
const task = await prisma.task.delete({
//タスクを削除する
where: {
id: taskId,
},
})
res.status(200).json({
//削除が成功したらそのタスクのIDを200ステータスで返す
message: 'タスクが正常に削除されました',
deletedTaskId: task.id,
})
} catch (error) {
if (error.code === 'P2025') {
//タスクを見つからない場合P2025エラー
res.status(404).json({ message: 'タスクが見つかりませんでした' }) //404ステータスを返す
} else {
console.error('タスクの削除に失敗しました:', error)
res.status(500).json({ message: 'タスクの削除に失敗しました' })
}
}
})
//タスクの詳細を取得する
router.get('/:id/details', async (req, res) => {
const taskId = parseInt(req.params.id)
try {
// タスクをIDで検索し、関連するユーザー情報も取得する
const task = await prisma.task.findUnique({
where: {
id: taskId,
},
include: {
user: true, // ユーザー情報を一緒に取得
},
})
if (!task) {
return res.status(404).json({ message: 'タスクが見つかりませんでした' })
}
// タスクの詳細情報を返す
res.status(200).json({
taskId: task.id,
title: task.title,
description: task.description,
deadline: task.deadline,
user: {
userId: task.user.id,
userName: task.user.name,
userEmail: task.user.email,
},
})
} catch (error) {
console.error('タスクの詳細取得に失敗しました:', error)
res.status(500).json({ message: 'タスクの詳細取得に失敗しました' })
}
})
// 全てのタスクを取得するAPI
router.get('/', async (req, res) => {
try {
// Prismaを使って全てのタスクを取得
const tasks = await prisma.task.findMany() // タスクを全件取得
// タスクが見つかった場合、200ステータスでデータを返す
res.status(200).json(tasks)
} catch (error) {
console.error('すべてのタスクの取得に失敗しました:', error)
res.status(500).json({ message: 'すべてのタスクの取得に失敗しました' })
}
})
module.exports = router //このファイルで定義したルートを他のファイルで使えるようにする
ndex.jsファイル
index.jsファイル
const express = require('express')
const { Pool } = require('pg')
require('dotenv').config()
const cors = require('cors')
const app = express() //app というサーバーを作るためのオブジェクト(インスタンス)を作る
// この行を追加
app.use(express.json()) //受け取ったデータをJSONに変換する
app.use(cors()) //他のウェブサイトやアプリからのアクセスを許可
const port = process.env.PORT || 4000 //サーバーが動作するポート番号を決める
const usersRouter = require('./controllers/users') //(users.js)を読み込んで、usersRouter で使えるようにする
const tasksRouter = require('./tasks/tasks') //(tasks.js)を読み込んで、tasksRouter で使えるようにする
app.use(express.json())
console.log(`test `) //コンソールにtestと表示させている
const pool = new Pool({
connectionString: process.env.DATABASE_URL || '', //PostgreSQLに接続するための設定
})
console.log(process.env.DATABASE_URL)
app.get('/', async (req, res) => {
/// というURLにアクセスしたときに動く処理
try {
const { rows } = await pool.query('SELECT current_database()')
console.log(rows)
res.send(`Server time is: ${JSON.stringify(rows, null, 2)}`)
} catch (err) {
res.status(500).send('Database error')
}
})
app.use('/api/users', usersRouter) // /api/users で始まるリクエストを usersRouter に渡して処理
app.use('/tasks', tasksRouter) ///tasks で始まるリクエストを tasksRouter に渡して処理
app.listen(port, () => {
console.log(`Server running on port ${port}`) //サーバーを指定したポート番号で動作させる
})
ルーターのエクスポート
全てのルート定義の後に以下の行を確認してください。
module.exports = router;
schema.prisma ファイル
schema.prismaファイル
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
password String
userIcon String? // ユーザーアイコンのURL(省略可能)
Task Task[]
}
model Task {
id Int @id @default(autoincrement())
userId Int
title String
description String?
deadline DateTime
user User @relation(fields: [userId], references: [id])
}