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?

【Next.js】API RouteからSpring BootへPOST通信をする | ポートフォリオ開発記録#3

0
Posted at

まえがき

前回は入力項目からの情報取得を行いました。
今回はいよいよAPIによるSpring Bootへの値渡しを行います。

本文

この記事の対象者

ここも第1回とほぼ同じですね。
要するに、完全未経験を除いた初心者です。
今回はSpringBootも入るのでJavaもある程度理解している必要があります。

前提

以下インストール済(インストール方法は省略)
・React 19.0.0
・Next.js 16.0.0
・App Router
・Java 21
・Spring Boot 4.1

1.フロントエンド

第2回でも書きましたが、handleLoginでやっているとします。
また、importはすでにやっているとして省略します。

  const handleLogin = async (event: FormEvent<HTMLFormElement>) => {
    (チェック条件は省略)
    event.preventDefault() // GET /?userId=... を防ぐ

  const response = await fetch("/api/request/login", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      userId,
      password,
    }),
  });

これで"/api/request/login"に向かってPOSTのJSONが発信され、その中身がuserIdとpassword(事前にuseStateで取得済みとする)であるとわかります。

2.フロントエンドからAPIの発行

上記をもとにRoute Handlerがリクエストを受け取るので、
以下のようなソースを用意してSpring Bootに転送します。

route.ts
import { NextResponse } from 'next/server';

type LoginRequest = {
  userId: string;
  password: string;
};

export async function POST(request: Request) {

  const body: LoginRequest = await request.json();

  const springResponse = await fetch(
    `${process.env.SPRING_BOOT_API_BASE_URL}/api/request/login`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    },
  );

  const data = await springResponse.json();

  const response = NextResponse.json(data, {
  status: springResponse.status,
  });

  return response;

}

ちなみに、これはAPI Routeによるもので、パスは以下になります。

app/
 api/
  request/
   login/
    route.ts

それと、{process.env.SPRING_BOOT_API_BASE_URL}は環境変数です。

3.Spring Boot:Controller

次にSpring Boot側です。

"/api/request/login"に向かってきているので、以下のようなControllerになります。
※importなどは省略

@RestController
@RequestMapping("/api/request")
public class LoginController {

    private final LoginService loginService;

    public LoginController(
            LoginService loginService,
    ) {

        this.loginService = loginService;
    }

    // ログインリクエストを用いてログイン処理を行う
    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(
            @RequestBody LoginRequest request
    ) {
        LoginResponse response = loginService.login(request);

        // ログイン失敗時のレスポンス
        if (!response.result()) {
            return ResponseEntity
                    .status(401)
                    .body(new LoginResponse(false,
                            "IDまたはパスワードが違います"));
        }

        return ResponseEntity.ok()
                .body(new LoginResponse(true, "ログイン成功"));

    }

SpringBoot初学者のために補足しておくと、
@RequestMappingと@PostMappingの引数を合わせると
/api/request/loginになります。
もちろん、Next.jsとSpring Bootではローカルの場合でもポート番号が違う点は注意です。

そして、POSTのJSONだからこれは@PostMappingであり、GETの場合だったら@GetMappingでしょう。

4.Controllerに必要な部品

さて、上記Controllerで必要な部品があります。
LoginServiceとLoginRequestとLoginResponseです。

※以下はPasswordEncoderでパスワードのハッシュ化を実行しています。
※UserAccountRepositoryはMapperファイルであり、MyBatisと接続しています。
 今回はSpring Bootの勉強ではないのでそちらは省略。
※UserAccountはユーザ情報のBeanです。

@Service
public class LoginService {

    private final UserAccountRepository userAccountRepository;
    private final PasswordEncoder passwordEncoder;

    public LoginService(
            UserAccountRepository userAccountRepository,
            PasswordEncoder passwordEncoder
    ) {
        this.userAccountRepository = userAccountRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public LoginResponse login(LoginRequest request) {
        // requestのユーザIDの値を用いて一意なユーザアカウントを見つける
        UserAccount user = userAccountRepository
                .findByUserId(request.userId());

        // ユーザが見つからない場合
        if (user == null) {
            return new LoginResponse(
                    false,
                    "ユーザ名が正しくありません。"
            );
        }

        boolean passwordMatches = passwordEncoder.matches(
                request.password(),
                user.getPassword()
        );

        // パスワードが合致しなかった場合
        if (!passwordMatches) {
            return new LoginResponse(
                    false,
                    "パスワードが正しくありません。"
            );
        }

        return new LoginResponse(true, null);
    }


// ログイン画面からの受信(Next.js→Spring Boot)
public record LoginRequest(
    @NotBlank(message = "ユーザIDは必須です")
    @Size(min = 8, max = 16,message = "ユーザIDは8文字以上16文字以内で入力してください")
    @Pattern(regexp = "[!-~]{8,16}$",
     message = "ユーザIDは半角英数字と記号のみ使用可能です")
    String userId,
    @NotBlank(message = "パスワードは必須です")
    @Size(min = 8, max = 16,message = "パスワードは8文字以上16文字以内で入力してください")
    @Pattern(regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[^A-Za-z0-9])[!-~]{8,16}$",
     message = "パスワードは半角英大文字・半角英小文字・半角数字・半角記号をそれぞれ1種類以上含む必要があります")
    String password
) {
}
// ログイン画面への送信(Next.js←Spring Boot)
public record LoginResponse(
    boolean result,
    String reason
) {
}

ここでrecordクラスについて補足します。

主にデータを保持するためのクラス(DTOに役立つ)です。

従来のJavaBeansやPOJOのように、getter/setterなどを手動で実装する必要がなく、1行の宣言で自動生成されるので、便利です。

ただ、get○○()/set○○()ではなく、フィールド名と同じ名前のメソッドになりますので、ご注意を。

また、Java 16から正式に導入されましたので、それ以前のレガシー環境だと実装できません。

5.フロントエンドでの結果受け取り

ようやくフロントに戻ってきました。

(1.で書いたhandleLoginの続き)
  const result = await response.json();

  if (result.result) {
    (ログイン処理)
  } else {
    alert(result.reason);
  }

リクエストのresultがboolean型なので失敗していたらreasonの中身がアラートで表示されます。
ちなみに、リクエスト情報は一度使用すると消えちゃうみたいなので、alertやconsole.logなどで確認したい場合などには.cloneメソッドで複製するのがよいです。

あとがき

さて、これだと認証は成功しますが、実はリンクを直打ちすると通ってしまうのです。
はい、これだと認証の意味がありません。
ですので、次回にCookieに認証情報を乗せる方法を書きます。

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?