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?

【Spring】REST APIの作成②POSTで新規登録

Posted at

概要

以下のような処理を非同期(JavaScriptのfetch)で行う登録機能を実装します。

  1. 単語帳名をフォームに入力し「追加する」ボタンをクリック

  2. バリデーション&DB登録処理(Spring Boot側)を実行

  3. 成功すれば、登録された単語帳名を一覧に即時反映
     (エラーがあればエラーメッセージを表示)

※spring securityを導入している前提

restPost.png

■ フロント側

SpringSecurityを導入しているため,POST,DELETE,PUTメソッドの送信にはCSRFトークンが必須になる。CSRFトークンの値はSpring Securityが自動でThymeleafに埋め込んで送信してくれるが、今回はJSでfetchする際にトークンの値を合わせて送信したいので、HTMLに手動で埋め込む。

html

	<!-- 登録用フォーム -->
	<form th:action="@{/wordbooks/api/regist}" th:object="${wordbookForm}">
    	<label>単語帳名:<input type="text" th:field="*{wordbookName}"></label>
        <input type="hidden" id="csrfToken" th:value="${_csrf.token}" />
        <!-- バリデーションエラーメッセージ表示 -->
		<ul id="errorMsgList" class="errorMsgHidden"></ul>
        
		<button type="submit" id="registBtn">追加する</button>
	</form>

JavaScript

form.addEventListener("submit", async (event) => {
   
	event.preventDefault(); //formでのリクエスト送信をキャンセル
	errorMsgList.innerHTML = "";//エラーメッセージをいったん白紙に
    
    //各パラメータの値を取得
	const wordbookName = document.getElementById("wordbookName").value.trim();
    const csrfToken = document.getElementById("csrfToken").value;
    
	try {
		const res = await fetch(`/wordbooks/api/regist`, {
			method: "POST",
			headers: {
				"Content-Type": "application/x-www-form-urlencoded",//フォームで送る      
				"X-CSRF-TOKEN": csrfToken//CSRFトークンもちゃんと送る
			},
			body: `wordbookName=${encodeURIComponent(wordbookName)}`
            //単語帳名の入力文字に"?"や"="が含まれていても入力文字として認識されるようエンコードする
		});
		if (res.ok) {
			const wordbook = await res.json();
            
			 // ここに単語帳一覧に表示する処理を記述する
             
		} else {
			const errorMessages = await res.json();

            //ここにエラーメッセージを表示する処理を記述する
		}
	} catch (error) {
		alert("登録に失敗しました");
	}
});

■ サーバ側(バリデーションクラス)

SpringのValidatorインタフェースを使い、DBに重複する単語帳名が存在するかチェックする。

@Component
@RequiredArgsConstructor
public class WordbookValidator implements Validator{
	private final WordbookService wordbookService;

	@Override
	public boolean supports(Class<?> clazz) {
		return WordbookForm.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		WordbookForm wordbookForm = (WordbookForm) target;
        //単語帳名でDB検索
		Optional<Wordbook> wordbookOpt = wordbookService.findByWordbookName(wordbookForm.getWordbookName());
        //存在した場合はエラー発生
		if(wordbookOpt.isPresent()) {
			errors.rejectValue("wordbookName", null, "この単語帳名は既に登録されています");
		}			
	}
}

■ サーバ側(コントローラ)

ResponseEntityクラスを使うと、HTTPレスポンスのステータスコード・ヘッダー・ボディを自由にカスタマイズできる。フロント側で更新処理の成功or失敗で挙動を分けたい場合、サーバ側(コントローラ)はステータスコードを明示的に指定してレスポンスを返してあげればよい。

@RestController
@RequiredArgsConstructor
public class WordbookApiController {
	
	private final WordbookService wordbookService;
    private final WordbookValidator wordbookValidator;
    
	//自作のバリデータをバインド
	@InitBinder("wordbookForm")
	public void initBinder(WebDataBinder binder) {
		binder.addValidators(wordbookValidator);
	}
	
	@PostMapping("/regist")
	public ResponseEntity<?> registWordBook(
			@AuthenticationPrincipal LoginUserDetails loginUserDetails,
			@Validated WordbookForm wordbookForm,
			BindingResult bindingResult) {

		if (bindingResult.hasErrors()) {
	        // エラーメッセージのリストを作成
	        List<String> errorList = bindingResult.getAllErrors().stream()
	            .map(error -> error.getDefaultMessage())
	            .collect(Collectors.toList());
	        // 400 Bad Request でエラーリストを返す
	        return ResponseEntity.badRequest().body(errorList);
	    }
		//登録処理
		Wordbook wordbook = new Wordbook();
		wordbook.setUser(loginUserDetails.getUser());
		wordbook.setName(wordbookForm.getWordbookName());
		WordbookDto dto = wordbookService.save(wordbook);
		return ResponseEntity.ok(dto);
	}

■ クライアント側(JS)でJSONを取得する

//fetch処理の続き
	if (res.ok) {
			const wordbook = await res.json();       
            //一覧リストに追加する関数
			addWordbookToList(wordbook.wordbookName);
		} else {		
			//バリデーションエラーがあるとき
			errorMsgList.classList.replace("errorMsgHidden","errorMsgVisible");
			const errorMessages = await res.json();
			for(const msg of errorMessages){
				const li = document.createElement("li");
				li.textContent = msg;
				errorMsgList.append(li);
			} 
		}
	} catch (error) {
		alert("登録に失敗しました");
	}
})
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?