AJAX(Asynchronous JavaScript And XML)는 페이지 전체를 새로고침하지 않고 서버와 데이터를 주고받을 수 있는 기법입니다.
지금은 XML 대신 JSON을 주로 사용하며, 구현은 XMLHttpRequest(전통 방식), Fetch API(현대 방식), jQuery $.ajax()(레거시 방식) 등으로 할 수 있습니다.
검색 자동완성, 댓글 등록 후 새로 반영, 무한 스크롤 등이 전형적인 사례입니다.
예제들은 모두 공개 테스트 API인 jsonplaceholder를 사용하므로 안전하게 실행할 수 있습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>AJAX - XHR GET</title>
<style>
body{font-family:system-ui,apple-system,Segoe UI,Roboto,Arial; padding:24px;}
button{padding:8px 12px; border:1px solid #ccc; background:#fff; cursor:pointer; border-radius:8px;}
.card{margin-top:16px; padding:16px; border:1px solid #eee; border-radius:12px;}
.small{color:#666; font-size:12px;}
.loading{opacity:.6;}
.error{color:#c0392b;}
</style>
</head>
<body>
<h1>XMLHttpRequest로 데이터 불러오기</h1>
<p class="small">엔드포인트: https://jsonplaceholder.typicode.com/posts/1</p>
<button id="loadBtn">데이터 불러오기</button>
<div id="result" class="card">아직 불러오지 않았습니다.</div>
<script>
const btn = document.getElementById("loadBtn");
const box = document.getElementById("result");
btn.addEventListener("click", () => {
box.textContent = "불러오는 중입니다...";
box.classList.add("loading");
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1", true);
xhr.onload = function () {
box.classList.remove("loading");
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
box.innerHTML = `
<h3>${data.title}</h3>
<p>${data.body}</p>
<div class="small">id: ${data.id}</div>
`;
} catch (e) {
box.innerHTML = `<span class="error">JSON 파싱 오류입니다.</span>`;
}
} else {
box.innerHTML = `<span class="error">요청 실패입니다. (status: ${xhr.status})</span>`;
}
};
xhr.onerror = function () {
box.classList.remove("loading");
box.innerHTML = `<span class="error">네트워크 오류입니다.</span>`;
};
xhr.send();
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>AJAX - Fetch GET</title>
<style>
body{font-family:system-ui,apple-system,Segoe UI,Roboto,Arial; padding:24px;}
button{padding:8px 12px; border:1px solid #ccc; background:#fff; cursor:pointer; border-radius:8px;}
.card{margin-top:16px; padding:16px; border:1px solid #eee; border-radius:12px;}
.small{color:#666; font-size:12px;}
.loading{opacity:.6;}
.error{color:#c0392b;}
</style>
</head>
<body>
<h1>Fetch API로 데이터 불러오기</h1>
<p class="small">엔드포인트: https://jsonplaceholder.typicode.com/posts/2</p>
<button id="fetchBtn">데이터 불러오기</button>
<div id="fetchResult" class="card">아직 불러오지 않았습니다.</div>
<script>
const fetchBtn = document.getElementById("fetchBtn");
const fetchBox = document.getElementById("fetchResult");
fetchBtn.addEventListener("click", async () => {
fetchBox.textContent = "불러오는 중입니다...";
fetchBox.classList.add("loading");
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts/2");
if (!res.ok) throw new Error("응답 에러: " + res.status);
const data = await res.json();
fetchBox.classList.remove("loading");
fetchBox.innerHTML = `
<h3>${data.title}</h3>
<p>${data.body}</p>
<div class="small">id: ${data.id}</div>
`;
} catch (err) {
fetchBox.classList.remove("loading");
fetchBox.innerHTML = `<span class="error">실패했습니다: ${err.message}</span>`;
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>AJAX - jQuery GET</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<style>
body{font-family:system-ui,apple-system,Segoe UI,Roboto,Arial; padding:24px;}
button{padding:8px 12px; border:1px solid #ccc; background:#fff; cursor:pointer; border-radius:8px;}
.card{margin-top:16px; padding:16px; border:1px solid #eee; border-radius:12px;}
.small{color:#666; font-size:12px;}
.loading{opacity:.6;}
.error{color:#c0392b;}
</style>
</head>
<body>
<h1>jQuery.ajax()로 데이터 불러오기</h1>
<p class="small">엔드포인트: https://jsonplaceholder.typicode.com/posts/3</p>
<button id="jqBtn">데이터 불러오기</button>
<div id="jqResult" class="card">아직 불러오지 않았습니다.</div>
<script>
$("#jqBtn").on("click", function () {
const $box = $("#jqResult").text("불러오는 중입니다...").addClass("loading");
$.ajax({
url: "https://jsonplaceholder.typicode.com/posts/3",
method: "GET",
success: function (data) {
$box.removeClass("loading").html(`
<h3>${data.title}</h3>
<p>${data.body}</p>
<div class="small">id: ${data.id}</div>
`);
},
error: function (xhr) {
$box.removeClass("loading").html(`<span class="error">실패했습니다. (status: ${xhr.status})</span>`);
}
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>AJAX - Fetch POST (댓글)</title>
<style>
body{font-family:system-ui,apple-system,Segoe UI,Roboto,Arial; padding:24px; max-width:720px; margin:auto;}
form{display:flex; gap:8px;}
input, button{padding:10px; border:1px solid #ccc; border-radius:8px; font-size:14px;}
button{background:#fff; cursor:pointer;}
ul{margin-top:16px; padding:0; list-style:none;}
li{padding:10px 12px; border:1px solid #eee; border-radius:10px; margin-bottom:8px;}
.small{color:#666; font-size:12px;}
.error{color:#c0392b;}
.pending{opacity:.6;}
</style>
</head>
<body>
<h1>댓글 등록 (새로고침 없이)</h1>
<p class="small">POST: https://jsonplaceholder.typicode.com/comments</p>
<form id="commentForm">
<input id="commentInput" type="text" placeholder="댓글 입력" required />
<button type="submit">등록</button>
</form>
<ul id="commentList"></ul>
<div id="msg" class="small"></div>
<script>
const form = document.getElementById("commentForm");
const input = document.getElementById("commentInput");
const list = document.getElementById("commentList");
const msg = document.getElementById("msg");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) {
msg.textContent = "댓글을 입력해주세요.";
return;
}
const tempId = "temp-" + Date.now();
const li = document.createElement("li");
li.dataset.id = tempId;
li.classList.add("pending");
li.textContent = text + " (전송 중...)";
list.prepend(li);
input.value = "";
msg.textContent = "";
try {
const res = await fetch("https://jsonplaceholder.typicode.com/comments", {
method: "POST",
headers: { "Content-Type": "application/json; charset=UTF-8" },
body: JSON.stringify({ body: text, postId: 1 })
});
if (!res.ok) throw new Error("응답 에러: " + res.status);
const data = await res.json();
li.classList.remove("pending");
li.textContent = data.body + " (id: " + data.id + ")";
msg.textContent = "등록 성공입니다!";
} catch (err) {
li.remove();
msg.innerHTML = `<span class="error">등록 실패입니다: ${err.message}</span>`;
}
});
</script>
</body>
</html>
XMLHttpRequest, fetch API, jQuery.ajax 등 다양한 방식으로 사용할 수 있습니다.