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?

作成したログイン機能をNextjsに移す

Last updated at Posted at 2024-10-14

はじめに

以前作成した ログインしたユーザーが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に変換する際、以下の変更が必要です

  1. class属性をclassNameに変更します。
  2. 自己閉じタグにはスラッシュを追加します(例:< img />)。
  3. 属性値が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])
}


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?