まえがき
前回は入力項目からの情報取得を行いました。
今回はいよいよ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に転送します。
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.フロントエンドでの結果受け取り
ようやくフロントに戻ってきました。
const result = await response.json();
if (result.result) {
(ログイン処理)
} else {
alert(result.reason);
}
リクエストのresultがboolean型なので失敗していたらreasonの中身がアラートで表示されます。
ちなみに、リクエスト情報は一度使用すると消えちゃうみたいなので、alertやconsole.logなどで確認したい場合などには.cloneメソッドで複製するのがよいです。
あとがき
さて、これだと認証は成功しますが、実はリンクを直打ちすると通ってしまうのです。
はい、これだと認証の意味がありません。
ですので、次回にCookieに認証情報を乗せる方法を書きます。