4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js×CakePHP】初心者がフロントエンドとバックエンドを繋げてみた【初投稿】

Posted at

初めまして。今年からエンジニアになりましたポチです。

自分の知ったこと, やったことの備忘録も含めて記事を書いてみることにしました。
数ヶ月前に終わった会社の新人研修で学んだことの復習をしました。
フロントエンドをNext.js(React)で作った後、バックエンドをCakePHPを使用して、フロントに文字を表示させてみます。
未熟な身ですが読んでいただければ嬉しいです!

はじめに

この記事でできること

Reactで作成したフロントエンドをバックエンド(CakePHP)と繋げていきます

対象者

フロントエンドとバックエンドを繋げてみたい方
(あまり参考になることは書けませんでしたが...)

前提条件

フロントをNext.jsを用いて以下のように作成してあること
*ボタンやテキストフィールドなどはMaterialUIを使用しています。importして配置するだけで綺麗な画面が作成できるので便利です。
リンク先はボタンのコンポーネントがまとまっているページです。

こちらはルーティング↓

app/Sampleapp/page.tsx
import { SampleappView } from "../../views/SampleappView"
export default function Sampleapp() {
  return (
    <SampleappView />
  )
}

こちらは見た目とロジック↓(本来はロジックをhooksへ切り離す方が良いのですが...。)

app/views/SampleappView/index.tsx
"use client"

import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";

export function SampleappView() {
  return (
    <div style={{ maxWidth: 400, margin: "40px auto", textAlign: "center" }}>
      <h1>SampleApp</h1>
      <div style={{ marginTop: 15, display: "flex", justifyContent: "center", gap: 10 }}>
      <TextField id="outlined-basic" label="入力欄" variant="outlined" />
      <Button variant="contained">ボタン</Button>
      </div>

    </div>
  )
}

現在は画像の通り、テキストフィールドとボタンがあるだけの、見た目だけの状態です。
ボタンには何の関数も設定していないので、入力してもボタンを押しても何も起こりません。
スクリーンショット 2025-12-07 22.27.13.png

必要なことを追加していく

CakePHP側の準備

1.CakePHPをインストール

2.ルートを追加する

config/routes.php
$routes->connect('/api/echo-message', ['controller' => 'Api', 'action' => 'echoMessage']);

これは、第一引数/api/echo-messageにアクセスがあった時、
第二引数に書かれたApiControllerechoMessage()メソッドを実行する、
ということを表します。
echo()メソッドについては 3.Controllerを追加する で実装していきます。

3.Controllerを追加する
src/Controller配下にApiController.phpファイルを作成します。

ApiController.php
<?php
declare(strict_types=1);

namespace App\Controller;

use Cake\Controller\Controller;

class ApiController extends Controller
{
        public function echoMessage()
    {
        // POSTリクエストのみを許可
        $this->request->allowMethod(['post']);

        // クライアントから送られてきた 'message' を取得
        $message = $this->request->getData('message');

        return $this->response
            ->withStatus(200)
            ->withType('application/json')
            ->withStringBody(json_encode(['message' => $message],JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }
}

Reactにstateを追加

先ほど作ったルーティングをフロントから呼べるようにします。
まだ入力欄やボタンに機能をつけていなかったので、つけていきます。

index.tsx
"use client"

import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import { useState } from "react"; // 追加①

export function SampleappView() {
    const [message, setMessage] = useState(""); // 追加①
    const [result, setResult] = useState(""); // 追加②
    const handleClick = () => { 
    console.log("ボタンがクリックされました"); 
    } // 追加③
    
  return (
    <div style={{ maxWidth: 400, margin: "40px auto", textAlign: "center" }}>
      <h1>SampleApp</h1>
      <div style={{ marginTop: 15, display: "flex", justifyContent: "center", gap: 10 }}>
      <TextField label="入力欄" value={message} onChange={(e) => setMessage(e.target.value)} /> {/* 追加① */}
      <Button variant="contained" onClick={handleClick}>ボタン</Button> {/* 追加③ */}
      </div>

      {result && ( 
        <p style={{ marginTop: 20 }}>ここに表示:{result}</p> 
      )} {/* 追加② */}
    </div>
  )
}

いくつかコードを追加しました。
<追加①>
const [message, setMessage] = useState("");
value={message} onChange={(e) => setMessage(e.target.value)}
→初期値を''(空文字)に設定したstateのmessageをTextFieldのvalueに設定します。TextFieldの入力が変わるたびに setMessage によってmessage(stateの値)が更新されます。
useStateはReact特有の関数であるため、5行目でimportして使用します。
<追加②>
APIからの返却値を表示するためにresultというstateを用意しました。こちらも初期値を空文字に設定し、値がある場合のみ表示されるようにしました。
<追加③>
ボタンがクリックされたときの処理を書くために handleClick 関数を用意しました。
後ほどこの中で API を呼び出します。
現時点では、クリックしたことが確認できるようにconsole.log("ボタンがクリックされました");と入れておきました。

CakePHPと連携させる

index.tsx(抜粋)
 const handleClick = async() => {
        console.log("ボタンがクリックされました");
        const response = await fetch("http://localhost:8765/api/echo-message", {
        method: "POST",
        headers: {
        "Content-Type": "application/json",
        },
        body: JSON.stringify({
        message: message}),
        });

        const data = await response.json();
        setResult(data.message);
    }; // 追加③

このようにhandleClick関数の中に、APIを呼び出す処理を追加しました。

3行目fetchでブラウザからAPIを呼び出しているのですが、これは時間がかかる非同期処理です。そのため、関数にasync,関数の中でawaitを使うことで、レスポンスが返ってくるまで待っています。
URLの部分には、2.ルートを追加するで実装したconfig/routes.phpファイルと同じルートを設定します。
methodにはHTTPメソッド、headersでは送信データの形式を、bodyではAPIに送るデータを指定します。json形式であれば、今回のように異なる言語でもやりとりができるため、JSON.stringify関数を使用してオブジェクトをjson形式に変換しています。

const data = await response.json();
setResult(data.message);

最後の2行の部分です。1行目でAPIからのレスポンスを受け取ります。response.json()を使用して、JSON形式のデータをJavaScriptで使える形に変換します。こちらも非同期処理なのでawaitを使用しています。
そして、APIから返ってきたmessageresultというstateに保存します。

ボタンを押してみる

こうすると、出来た気がします。フロントで作ったテキストフィールドに文字を入力し、ボタンを押してみましょう。
...ボタンをクリックしても何も反応が無いようです。
本当にボタンを押せているのか、F12キーを押して開発者ツールを確認してみましょう。

スクリーンショット 2025-12-20 12.10.45.png
ボタンがクリックされましたと出ているので、ちゃんとクリックできていることが確認できますが、その下にいくつかエラーが書かれています。
どうやらCORSによるエラーのようです。

エラーを直す

...魔法の力で直しました。
(ここに書くには理解が足らなかったので、いつか勉強します。)

返答を確認する

スクリーンショット 2025-12-25 22.07.07.png
表示できました!
ボタンクリックと同時に入力欄を空文字にしたり、削除したり...等まだまだやることはたくさんありそうですが、今回の「フロントエンドとバックエンドを繋げる」という目標は達成しました。

あとがき

途中でCORSエラーとかCSRFとかに引っかかってしまい、まだ理解できていないのでいつか勉強したいですね。
今後このファイルにもう少し機能つけたり、API増やしたりリファクタリングできたらなーと思います。

最後までお読みいただきありがとうございました。
質問・アドバイス等あればコメントいただけると嬉しいです。
また次回の記事でお会いしましょう(お会いできるよう頑張ります)!

参考: これを使ってNext.jsの環境構築しました
https://zenn.dev/keita_max/books/863d0962f11eab/viewer/242d66

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?