AJAX는 비동기 통신을 위한 기술의 하나로,
자바스크립트 코드만으로 서버에 요청이 가능하도록 만들기 위해 고안된 개념입니다
일반적으로 웹 페이지가 서버로부터 정보를 요청하기 위해서는 전체 페이지를 새로고침하는 경우가 많습니다
하지만 AJAX를 이용하면 서버와 통신할 때 페이지 전체가 아닌
객체화한 데이터만을 주고받기 때문에,
웹 페이지 전체를 다시 로딩하지 않고도 웹 페이지의 일부분만을 갱신할 수 있습니다
즉 URL 갱신 없이 요청과 응답이 이루어진다는 것이 핵심입니다
xhr.send
> 응답은 xhr.onload
메서드를 사용)
아래는 예제 코드입니다
[server]
const express = require("express");
const app = express();
const nunjucks = require("nunjucks");
app.use(express.static("public"));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// 정적파일 연결을 위한 '라우터'를 생성합니다 ~ /js/index.js
app.set("veiw engine", "html");
nunjucks.configure("views", {
express: app,
});
app.get("/", (req, res) => {
res.render("index.html");
});
app.get("/ajax", (req, res) => {
console.log("ajax get");
const userid = req.query.userid;
let flag = true;
if (userid === undefined) {
flag = false;
}
// userid 받아와서 true or false
res.status(200).send(`${flag}`);
});
app.post("/ajax", (req, res) => {
console.log("ajax post");
const userid = req.body.userid;
let flag = true;
if (userid === undefined) {
flag = false;
}
console.log(flag, userid)
res.send(`${flag}`);
});
app.listen(3000, () => {
console.log("start server");
});
[client]
// XMLHttpRequest 요청
const xhr = new XMLHttpRequest();
// {open: ()=>{}, setRequestHeader: ()=>{}, send: ()=>{}, onload:()=>{}}
const ajax_get = document.querySelector("#ajax_get");
const ajax_post = document.querySelector("#ajax_post");
const msg = document.querySelector("#msg");
ajax_get.addEventListener("click", () => {
console.log(xhr);
xhr.open("get", "http://127.0.0.1:3000/ajax?userid=web7722"); // 인자값은 요청 메서드와 url(uri)입니다
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(); // send 메서드를 통해 요청합니다
xhr.onload = () => {
// onload 메서드를 통해 응답받습니다
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.response);
const response = xhr.response;
if (response === "true") {
msg.innerHTML = "아이디가 중복되었습니다";
msg.style.color = "red";
} else {
msg.innerHTML = "아이디를 사용할 수 있습니다";
msg.style.color = "green";
}
}
};
});
// userid web7722
ajax_post.addEventListener("click", () => {
xhr.open("POST", "http://127.0.0.1:3000/ajax");
// xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
// xhr.send("userid=web7722")
// 객체형태로 전달할 수도 있습니다. (server측에 json parser 필요!)
xhr.setRequestHeader("Content-Type", "application/json");
const data = { userid: "web7722" };
});
if (xhr.readyState === 4 && xhr.status === 200) {xhr.response}
xhr.readyState
: 값이 4일 때 응답이 성공적으로 이루어졌다는 뜻입니다xhr.status
: HTTP 요청의 상태코드입니다 (성공시 200)xhr.response
: 응답 바디 데이터를 객체 형태로 반환합니다 (실패시 null 반환)
비동기 통신에는 AJAX 말고도 fetch, axios등 다양한 방식이
있지만 코드의 사용법에 차이가 있을 뿐,
요청과 응답 과정을 url 변경 없이 처리한다는 점에서 활용성은 거의 같습니다
프론트엔드 서버와 백엔드 서버는 이름 그대로 전자는 GUI를,
후자는 데이터를 제공하는 역할을 담당합니다
비동기 통신으로 인해 화면을 그리는 것과 데이터를 주고받는 것의 구분이 확실해지면서
프로젝트의 규모가 커질수록 프론트엔드 서버를 따로 분리하는 경향성이 커졌습니다
이는 속도적인 측면에서도, 유지보수적인 측면에서도
각각의 역할에 맞게 서버를 분리할 때 얻는 장점이 크기 때문입니다
단, 프론트와 백엔드를 분리해서 통신을 하게되면 필연적으로
CORS 에러가 발생하게 됩니다
CORS 에러는 서로 다른 대상이 서버로부터 자료를 요청할 때
브라우저가 그 접근을 차단하면서 발생하는 에러입니다
예를 들어, A가 B의 자료를 요청할 경우 이는 출처가 다른 요청이 될 수 있습니다
이 때 브라우저가 그 요청을 차단하면서 CORS 에러를 발생시키는데,
CORS 에러가 발생하면 A는 B의 자원을 접근할 수 없게 됩니다
호스트는 물론 포트넘버만 달라도 다른 대상으로 취급받기 때문에
서버를 분리해서 사용하려면 별도의 CORS 처리가 필수적입니다
app.use((req, res, next) => {
// CORS 처리를 위한 작업입니다.
// *은 모든 요청을 허용하겠다는 의미. 메서드와 타입까지 정확히 기입해야 합니다
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, DELETE");
res.setHeader("Access-Control-Allow-Headers", "Content-type");
next();
});
위 코드는 cors 모듈을 설치하는 것으로도 대체 가능합니다
npm install cors
const cors = require("cors");
const app = express();
CORS 처리까지 숙지했다면 이제 간단한 수준으로
프론트엔드 서버와 백엔드 서버 분리작업을 실제로 해보겠습니다
디렉토리 구조를 프론트용과 백엔드용으로 나누어서 각각 분리된 서버 파일을 만들어야 합니다
그리고 다른 포트 넘버를 써서 동시에 서버를 실행하되
별도의 CORS 처리를 해야 합니다
브라우저의 GET 요청에 대해서는 프론트서버에서 응답을,
브라우저가 AJAX 방식으로 데이터를 요청할 경우에는
백엔드서버에서 다이렉트로 응답을 주도록 합니다
[backend server]
const express = require("express");
const cors = require("cors");
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// app.use((req, res, next) => {
// // CORS 처리를 위한 작업입니다.*은 모든 요청을 허용하겠다는 의미. 메서드와 타입까지 정확히 기입할 것.
// res.setHeader("Access-Control-Allow-Origin", "*");
// res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, DELETE");
// res.setHeader("Access-Control-Allow-Headers", "Content-type");
// next();
// });
// npm install cors로 대체 가능
app.use(cors());
const user = [
{
idx: 1,
userid: "web7722",
userpw: "1234",
username: "ingoo",
gender: "남자",
},
];
// 앞으로는 요청에 대해 페이지 전체가 이난 데이터만 string타입으로 건내야 합니다
// list
app.get("/users", (req, res) => {
// res.send(JSON.stringify(user));
// // express에 변환 기능이 내장되어 있지만 직접 형변환하는 것을 추천합니다
// ↓ 생략
res.json(user);
});
// write
app.post("/users", (req, res) => {
// console.log(req.body);
const { userid, userpw, username, gender } = req.body;
const response = { idx:user[user.length-1].idx+1, userid, userpw, username, gender};
// ㄴ DB를 거쳐온 응답 결과물 (repository > service > controller...)
user.push(response)
res.json(response)
});
// view 페이지 라우터 파라미터
app.get("/users/:idx", (req, res) => {
console.log(req.params);
const { idx } = req.params;
const [response] = user.filter(v=>v.idx === idx === parseInt(idx))
// idx가 일치하는 객체만 뽑아오기
res.json(response);
});
app.listen(3000, () => {
console.log("listening on port 3000");
});
[frontend]
const express = require("express");
const nunjucks = require("nunjucks");
const app = express();
app.use(express.static("public"));
app.set("view engine", "html");
nunjucks.configure("views", {
express: app,
});
app.get("/", (req, res) => {
res.render("index.html");
});
app.listen(3005, () => {
console.log("listening on port 3005");
});
그리고 AJAX 방식을 쓰는 이상 클라이언트는 자바스크립트를 통해서
서버에 필요한 자료를 요청할 수 있습니다
[client]
const ajax_get = document.querySelector("#ajax_get");
const ajax_post = document.querySelector("#ajax_post");
const msg = document.querySelector("#msg");
// request({ method: "get", path: "/users/5", body: "" }, ()=>{ ... })
const request = ({ method, path, body }, callback) => {
const host = "http://localhost:3000";
const xhr = new XMLHttpRequest();
xhr.open(method, `${host}${path}`);
xhr.setRequestHeader("Content-type", "application/json");
xhr.send(JSON.stringify(body));
xhr.onload = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
callback(xhr.response);
}
};
};
const userList = document.querySelector("#userlist");
const btn = document.querySelector("#btn");
const card = ({ idx, userid, userpw, username, gender }) => {
const ulElement = document.createElement("ul");
const idxElement = document.createElement("li");
const idElement = document.createElement("li");
const pwElement = document.createElement("li");
const nameElement = document.createElement("li");
const genderElement = document.createElement("li");
idxElement.innerHTML = idx;
idElement.innerHTML = userid;
pwElement.innerHTML = userpw;
nameElement.innerHTML = username;
genderElement.innerHTML = gender;
ulElement.append(
idxElement,
idElement,
pwElement,
nameElement,
genderElement
);
userList.append(ulElement);
};
request({ method: "get", path: "/users" }, (response) => {
const arr = JSON.parse(response);
console.log(arr);
arr.forEach((v) => {
card(v);
});
});
btn.addEventListener("click", () => {
request(
{
method: "post",
path: "/users",
body: {
userid: document.querySelector("#userid").value,
userpw: "1234",
username: "ingoo",
gender: "남자",
},
},
(response) => {
console.log(response);
card(JSON.parse(response));
}
);