Form 유효성 검사 + AJAX 제출 흐름 정리 (Bookmark 예제로 끝까지)

오병택·2026년 1월 21일
post-thumbnail

1) Form의 기본 동작 vs AJAX 제출

<form>은 기본적으로 submit이 발생하면:

  1. 브라우저가 HTML 기본 유효성 검사를 수행하고

  2. 통과하면 서버로 요청을 보내면서 페이지 이동/새로고침이 발생한다.

그런데 우리는 “페이지 이동 없이 서버 요청만” 하고 싶을 때가 많다(= AJAX).
이때 사용하는 방식이:

<form onsubmit="return addBookmarkRequest();">
  • return true → 기본 submit 동작 수행(페이지 이동)

  • return false → 기본 submit 동작 취소(페이지 유지)

-> 즉 AJAX만 하고 싶으면 return false로 기본 submit을 막는다.

2) HTML input 기본 유효성 검사 기능

브라우저가 기본 제공하는 검증 기능:

text 타입

minlength: 최소 길이

maxlength: 최대 길이

required: 필수 입력

number 타입

min, max: 숫자 범위

url 타입

url: URL 형식 검증

  • 장점: JS로 검증 로직을 다 만들지 않아도 되고, AJAX를 쓰면서도 그대로 활용 가능

3) Bookmark 예제 코드 실행 흐름

아래 흐름은 브라우저(HTML+JS) ↔ Spring 서버(Controller) 관점으로 나눠서 정리한다.

실습 예제

<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <form onsubmit="return addBookmarkRequest();">
            <label>즐겨찾기 이름 : </label><input type="text" name="name" minlength="2" maxlength="100" required><br>
            <label>즐겨찾기 URL : </label><input type="url" name="url" required><br>
            <input type="submit"><br>
        </form>
        <button onclick="getBookmarkListRequest();">즐겨찾기 목록 가져오기</button>
        <ol id="bookmark-list">
            <!-- 여기에 즐겨찾기 목록이 나옵니다. -->
        </ol>
        <script>
            function addBookmarkRequest() {
                const name = document.querySelector('input[name=name]').value;
                const url = document.querySelector('input[name=url]').value;
            const requestObject = {name: name, url: url};
            const requestJson = JSON.stringify(requestObject);

            function onReadyStateChange(event) {
                const currentAjaxRequest = event.currentTarget;

                if (currentAjaxRequest.readyState === XMLHttpRequest.DONE) {
                  if (currentAjaxRequest.status === 200) {
                    alert("즐겨찾기가 등록되었습니다.");
                  } else {
                    console.error('request failed');
                  }
                }
              }

            const ajaxRequest = new XMLHttpRequest();

            ajaxRequest.onreadystatechange = onReadyStateChange;
            ajaxRequest.open('POST', '/bookmark');
            ajaxRequest.setRequestHeader('Content-Type', 'application/json');
            ajaxRequest.send(requestJson);

            return false;
            }

            function getBookmarkListRequest() {
            function onReadyStateChange(event) {
                const currentAjaxRequest = event.currentTarget;

                if (currentAjaxRequest.readyState === XMLHttpRequest.DONE) {
                  if (currentAjaxRequest.status === 200) {
                    const bookmarkListDom = document.querySelector('#bookmark-list');
                    bookmarkListDom.innerHTML = '';

                    const bookmarks = JSON.parse(currentAjaxRequest.responseText);
                    bookmarks.forEach(bookmark => {
                        const liNode = document.createElement('li');
                        const textNode = document.createTextNode(bookmark.name + ' - ' + bookmark.url);
                        liNode.appendChild(textNode);
                        bookmarkListDom.appendChild(liNode);
                    });
                  } else {
                    console.error('request failed');
                  }
                }
              }

            const ajaxRequest = new XMLHttpRequest();

            ajaxRequest.onreadystatechange = onReadyStateChange;
            ajaxRequest.open('GET', '/bookmarks');
            ajaxRequest.send();
            }
        </script>
    </body>
</html>

A. 페이지 처음 열렸을 때

  1. 브라우저가 HTML을 렌더링한다.
  • 입력 폼(이름, URL)

  • submit 버튼

  • “즐겨찾기 목록 가져오기” 버튼

  • <ol id="bookmark-list"> 영역

  1. <script> 안의 함수(addBookmarkRequest, getBookmarkListRequest)가 등록된다.

-> 이 시점엔 아직 서버 요청이 자동으로 나가지 않는다.
(버튼 클릭/submit 이벤트가 있어야 호출됨)

B. “즐겨찾기 등록(POST /bookmark)” 흐름

1) 사용자가 submit 버튼 클릭

  • form의 submit 이벤트 발생

2) 브라우저가 HTML 유효성 검사 먼저 수행

예를 들어:

  • name: minlength="2", maxlength="100", required

  • url: type="url", required

검증 실패 → 브라우저가 안내 띄우고 제출 중단
검증 성공 → 다음 단계 진행

3) onsubmit 핸들러 실행

<form onsubmit="return addBookmarkRequest();">

→ addBookmarkRequest() 호출

여기서 핵심: 기본 submit(페이지 이동/새로고침)을 막고 AJAX만 수행하게 함

addBookmarkRequest() 부분에 대해서는 어제 올린 것에 상세하게 설명되어 있으니 스킵하겠습니다.

4) 서버(Spring)에서 처리 흐름

요청: POST /bookmark + JSON body

컨트롤러:

@RequestMapping(method = RequestMethod.POST, path = "/bookmark")
public String registerBookmark(@RequestBody Bookmark bookmark) {
    bookmarks.add(bookmark);
    return "registered";
}

서버 내부 흐름:

  1. /bookmark POST 라우팅 매칭

  2. @RequestBody가 JSON을 Bookmark 객체로 변환(역직렬화)

  3. bookmarks 리스트에 저장(메모리)

  4. "registered" 문자열 응답 반환 (보통 200 OK)

중요한 특징: DB 저장이 아니라 서버 메모리(List) 저장이라서
서버 재시작하면 데이터가 사라질 수 있음.

5) 브라우저가 응답을 받으면 콜백 실행

if (currentAjaxRequest.readyState === XMLHttpRequest.DONE) {
  if (currentAjaxRequest.status === 200) {
    alert("즐겨찾기가 등록되었습니다.");
  }
}

readyState === DONE일 때가 “응답까지 끝난 시점”

status === 200이면 성공 처리(alert)

여기까지가 “등록 완료” 흐름

C. “즐겨찾기 목록 조회(GET /bookmarks)” 흐름

1) 사용자가 버튼 클릭

<button onclick="getBookmarkListRequest();">즐겨찾기 목록 가져오기</button>

→ getBookmarkListRequest() 실행

2) getBookmarkListRequest() 내부(JS) 흐름

(1) XHR 생성 + 콜백 등록

const ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = onReadyStateChange;

(2) GET 요청 설정 + 전송

ajaxRequest.open('GET', '/bookmarks');
ajaxRequest.send();

3) 서버(Spring)에서 처리 흐름

컨트롤러:

@RequestMapping(method = RequestMethod.GET, path = "/bookmarks")
public List<Bookmark> getBookmarks() {
    return bookmarks;
}

서버 내부 흐름:

  1. /bookmarks GET 매핑

  2. bookmarks 리스트 반환

  3. 스프링이 List를 JSON 배열로 자동 변환(직렬화)해서 응답

응답 예시(JSON 문자열):

[
  {"name":"구글","url":"https://google.com"},
  {"name":"네이버","url":"https://naver.com"}
]

4) 브라우저가 응답 받으면 화면 렌더링

(1) 목록 영역 초기화

const bookmarkListDom = document.querySelector('#bookmark-list');
bookmarkListDom.innerHTML = '';

(2) responseText(JSON 문자열) → JS 배열로 파싱

const bookmarks = JSON.parse(currentAjaxRequest.responseText);

(3) DOM에 li 추가

bookmarks.forEach(bookmark => {
  const liNode = document.createElement('li');
  const textNode = document.createTextNode(bookmark.name + ' - ' + bookmark.url);
  liNode.appendChild(textNode);
  bookmarkListDom.appendChild(liNode);
});

여기까지가 “목록 조회 후 화면 표시” 흐름

4) 전체 흐름 한 줄 요약

등록(POST): submit → (HTML 유효성 검사) → JS가 JSON 만들어 전송 → 서버가 List에 저장 → 응답 → alert

조회(GET): 버튼 클릭 → 서버에서 List를 JSON으로 반환 → JS가 JSON.parse → <ol><li>로 렌더링

어제 이해를 제대로 못해서 조금 더 자세히 알기 위해 비슷한 내용이지만 빼먹은 부분 한번 더 정리함

profile
걱정하지 말고 일단 해봐!

0개의 댓글