CORS(Cross-Origin Resource Sharing) 란?
CORS는 웹 페이지가 다른 도메인의 리소스에 접근할 수 있도록 하는 메커니즘입니다.
웹 브라우저의 보안 정책인 동일 출처 정책(Same-Origin Policy) 때문에 웹 페이지는 자신과 동일한 출처에서만 리소스에 접근할 수 있습니다.
동일 출처 정책은 웹의 기본 보안 모델 중 하나로 악의적인 웹사이트가 사용자의 데이터를 탈취하거나 변조하는 것을 방지합니다.
하지만 현대 웹 애플리케이션들은 다양한 서비스와 데이터를 여러 도메인에서 가져와 사용하는 경우가 많은데 이러한 상황에서 동일 출처 정책만으로는 제한적인 사항이 많기 때문에 이러한 상황에서 안전하게 다른 출처의 리소스를 접근할 수 있게 만든 것이 CORS입니다.
설치 방법
npm install cors
주요 속성
const express = require("express");
const cors = require("cors");
const app = express();
// cors 설정
app.use(cors(
// origin : 특정 출처에 대한 접근 허용. 모두 다 허용하려면 true
origin : ["https://dbdj.tistory.com"],
// method : 허용되는 HTTP 메소드 지정
method : ["GET","POST"],
// allowedHeaders : 클라이언트가 보낼 수 있는 헤더 지정
allowedHeaders: ["Content-Type", "Authorization"],
// exposedHeaders : 클라이언트가 접근할 수 있는 응답 헤더 지정
exposedHeaders: ["Custom-Header"],
// credentials : 자격증명(cookie, http 인증)을 허용 여부 지정
// true로 설정하면 Access-Control-Allow-Credentials 헤더를 true 설정
credentials: true,
// maxAge : 사전 요청의 결과를 캐시할 시간을 초단위로 지정
maxAge: 600,
));
Axios 란?
axios는 브라우저와 Nodejs에서 사용되는 Promise 기반의 HTTP 비동기 통신 라이브러리 입니다.
axios는 현대 웹 개발에서 널리 사용되는 라이브러리로 브라우저와 Nodejs 모두에서 사용 가능하여 범용성이 높고 특히 SPA(Single Page Application)이나 API와의 통신을 위해 많이 사용되며 XMLHttpRequest를 기반으로 하지만 더 편리한 API와 추가적인 기능을 제공합니다.
주요 기능
Promise 기반 : axios는 promise 기반으로 동작하기 때문에 비동기 요청을 간결하게 처리합니다.
요청 및 응답 인터셉터 : 요청이나 응답을 가로채 수정하거나 로깅하는 등의 작업을 수행 할 수 있습니다.
요청 및 응답 데이터 변환 : 요청 데이터와 응답 데이터를 자동으로 변환해주는 기능이 있습니다.
요청 취소 : 이미 시작된 HTTP 요청을 취소하는 기능을 제공합니다.
HTTP 요청 병합 : 여러 요청을 병합하여 하나의 요청으로 처리할 수 있습니다.
CSRF 보호 : CSRF 토큰을 지원합니다.
JSON 데이터 자동 변환 : JSON 데이터를 자동으로 JavaScript 객체로 변환합니다.
Cors, Axios 실습
cors와 axios를 활용한 간단한 로그인, 회원가입, 게시판 기능을 구현
cors 설정을 위해 backEnd와 frontEnd로 파일을 나누고 frontEnd에서 live server extension을 활용하여 브라우저 연결
(backEnd는 8080포트 사용하고 frontEnd는 live server를 활용하기 때문에 5500 포트 사용하기 때문에 다른 도메인으로 인식)
프로젝트 구조
사용 모듈
npm init -y
npm install bcrypt cors dotenv express express-session jsonwebtoken mysql2 nodemon sequelize
Backend
config/index.js
데이터베이스에 연결하기 위한 기본 설정
const config = {
dev: {
// env 파일 안에 있는 정보를 불러와 데이터베이스에 연결
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
host: process.env.DATABASE_HOST,
dialect: "mysql",
},
};
module.exports = config;
// env는 이런식으로 설정되어 있고 위치는 항상 최상단에 위치해야함 (app.js와 같이 있음)
// DATABASE_USERNAME = [데이터베이스 관리자 아이디]
// DATABASE_PASSWORD = [데이터베이스 관리자 비밀번호]
// DATABASE_NAME = [스키마 이름]
controllers/loginController.js
로그인 유효성 검사와 로그인 성공시 토큰 생성
const { User } = require("../models");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
exports.Login = async (req, res) => {
const { user_id, user_pw } = req.body;
try {
const user = await User.findOne({
where: {
user_id,
},
});
if (user == null) {
return res.send("없는 아이디 입니다.");
}
const same = bcrypt.compareSync(user_pw, user.user_pw);
const { id, name, age } = user;
if (same) {
let token = jwt.sign(
{
id,
name,
age,
user_id,
},
process.env.ACCESS_TOKEN_KEY,
{
expiresIn: "20m",
}
);
req.session.accessToken = token;
// 여기서 / 경로는 백엔드의 도메인 경로
// 프론트 경로를 작성해야함
// 리다이렉트 할게 아니면 프론트에서 응답 받은 값으로 조건 분기 처리해서 페이지를 전환하면됨
// 배포된 프론트의 경로를 작성
res.redirect("http://127.0.0.1:5500/frontEnd/main.html");
} else {
return res.send("비밀번호 틀림");
}
} catch (error) {
console.error(error);
}
};
exports.viewUser = async (req, res) => {
const acc_decoded = req.acc_decoded;
console.log(acc_decoded);
try {
const user = await User.findOne({
where: { id: acc_decoded.id },
});
// json 형태로 데이터를 응답
res.json(user);
} catch (error) {
console.error(error);
}
};
controllers/postController.js
게시글 CRUD
const { User, Post } = require("../models");
exports.viewAllPost = async (req, res) => {
try {
const data = await Post.findAll();
res.json(data);
} catch (error) {
console.error(error);
}
};
exports.insertPost = async (req, res) => {
const { user_id } = req.acc_decoded;
const { title, content } = req.body;
try {
const getId = await User.findOne({
where: {
user_id,
},
});
await Post.create({
title,
content,
writer: user_id,
UserId: getId.id,
});
res.redirect("http://127.0.0.1:5500/frontEnd/posts.html");
} catch (error) {
console.error(error);
}
};
let postId;
exports.updatePage = async (req, res) => {
const { id } = req.params;
postId = id;
try {
res.redirect("http://127.0.0.1:5500/frontEnd/update.html");
} catch (error) {
console.error(error);
}
};
exports.selectPost = async (req, res) => {
try {
const data = await Post.findOne({ where: { id: postId } });
res.json(data);
} catch (error) {
console.error(error);
}
};
exports.updatePost = async (req, res) => {
const id = req.params.id;
const { title, content } = req.body;
try {
await Post.update({ title, content }, { where: { id } });
res.redirect("http://127.0.0.1:5500/frontEnd/posts.html");
} catch (error) {
console.error(error);
}
};
exports.myPage = async (req, res) => {
try {
res.redirect("http://127.0.0.1:5500/frontEnd/mypage.html");
} catch (error) {
console.error(error);
}
};
exports.myPosts = async (req, res) => {
const { acc_decoded } = req;
try {
const data = await Post.findAll({ where: { writer: acc_decoded.user_id } });
data.dataValues = data.map((i) => i.dataValues);
res.json(data);
} catch (error) {
console.error(error);
}
};
exports.myInfoPage = async (req, res) => {
try {
res.redirect("http://127.0.0.1:5500/frontEnd/myinfo.html");
} catch (error) {
console.error(error);
}
};
exports.getMyInfo = async (req, res) => {
const { acc_decoded } = req;
try {
const data = await User.findOne({ where: { name: acc_decoded.name } });
res.json(data);
} catch (error) {
console.error(error);
}
};
exports.myInfoUpdate = async (req, res) => {
const id = req.params.id;
const { name, age } = req.body;
try {
await User.update({ name, age }, { where: { id } });
res.redirect("http://127.0.0.1:5500/frontEnd/main.html");
} catch (error) {
console.error(error);
}
};
exports.deletePost = async (req, res) => {
const id = req.params.id;
try {
await Post.destroy({ where: { id } });
res.redirect("http://127.0.0.1:5500/frontEnd/posts.html");
} catch (error) {
console.error(error);
}
};
controllers/signUpController.js
회원가입 관련 설정 회원가입 시 패스워드는 암호화 되어 저장
const { User } = require("../models");
const bcrypt = require("bcrypt");
exports.signUp = async (req, res) => {
const { name, age, user_id, user_pw } = req.body;
try {
const user = await User.findOne({ where: { user_id } });
if (user != null) {
return res.send("사용중인 아이디 입니다.");
}
const hash = bcrypt.hashSync(user_pw, 10);
await User.create({
name,
age,
user_id,
user_pw: hash,
});
res.redirect("http://127.0.0.1:5500/frontEnd/login.html");
} catch (error) {
console.error(error);
}
};
middleware/loginMiddleware.js
로그인 유무 확인. 로그인이 필요한 작업에 대한 요청이 들어오면 수행
const jwt = require("jsonwebtoken");
exports.isLogin = async (req, res, next) => {
const { accessToken } = req.session;
try {
jwt.verify(
accessToken,
process.env.ACCESS_TOKEN_KEY,
(err, acc_decoded) => {
if (err) {
res.send("다시 로그인하세요.");
} else {
req.acc_decoded = acc_decoded;
next();
}
}
);
} catch (error) {
console.error(error);
}
};
models/index.js
데이터베이스 설정. 초기 테이블 생성
const Sequelize = require("sequelize");
const config = require("../config");
const User = require("../models/users");
const Post = require("../models/posts");
const sequelize = new Sequelize(
config.dev.database,
config.dev.username,
config.dev.password,
config.dev
);
const db = {};
db.sequelize = sequelize;
db.User = User;
db.Post = Post;
User.init(sequelize);
Post.init(sequelize);
User.join(db);
Post.join(db);
module.exports = db;
models/posts.js
Post 테이블 정의
const { DataTypes, Model } = require("sequelize");
const sequelize = require("../config");
class Post extends Model {
static init(sequelize) {
return super.init(
{
title: {
type: DataTypes.STRING(30),
allowNull: false,
},
content: {
type: DataTypes.STRING(100),
},
writer: {
type: DataTypes.STRING(20),
allowNull: false,
},
},
{
sequelize,
underscored: false,
timestamps: true,
modelName: "Post",
tableName: "cors_posts",
charset: "utf8",
collate: "utf8_general_ci",
}
);
}
static join(db) {
db.Post.belongsTo(db.User, { targetKey: "id" });
}
}
module.exports = Post;
models/users.js
User 테이블 정의
const Sequelize = require("sequelize");
class User extends Sequelize.Model {
static init(sequelize) {
return super.init(
{
name: {
type: Sequelize.STRING(20),
allowNull: false,
},
age: {
type: Sequelize.INTEGER,
allowNull: false,
},
user_id: {
type: Sequelize.STRING(20),
allowNull: true,
},
user_pw: {
type: Sequelize.STRING(64),
allowNull: true,
},
},
{
sequelize,
underscored: false,
timestamps: true,
modelName: "User",
tableName: "cors_users",
paranoid: false,
charset: "utf8",
collate: "utf8_general_ci",
}
);
}
static join(db) {
db.User.hasMany(db.Post, { sourceKey: "id" });
}
}
module.exports = User;
routers/loginRouter.js
로그인 관련 라우팅 설정
const router = require("express").Router();
const { Login, viewUser } = require("../controllers/loginController");
const { isLogin } = require("../middleware/loginMiddleware");
router.post("/", Login);
router.get("/view", isLogin, viewUser);
module.exports = router;
routers/postRouter.js
게시글 관련 라우팅 설정
const router = require("express").Router();
const { isLogin } = require("../middleware/loginMiddleware");
const {
viewAllPost,
insertPost,
updatePage,
selectPost,
updatePost,
myPage,
myPosts,
myInfoPage,
getMyInfo,
myInfoUpdate,
deletePost,
} = require("../controllers/postController");
router.get("/", isLogin, (req, res) => {
res.redirect("http://127.0.0.1:5500/frontEnd/posts.html");
});
router.get("/viewAll", isLogin, viewAllPost);
router.get("/insert", isLogin, (req, res) => {
res.redirect("http://127.0.0.1:5500/frontEnd/insert.html");
});
// 게시물 정보 수정 페이지 띄워주기
router.get("/update/:id", isLogin, updatePage);
// 선택한 게시물의 데이터를 가져와 띄워줌
router.get("/selectPost", isLogin, selectPost);
// 마이페이지 연결
router.get("/mypage", isLogin, myPage);
// 내가쓴 글 가져오기
router.get("/mypost", isLogin, myPosts);
// 내정보 변경 페이지
router.get("/myinfo", isLogin, myInfoPage);
// 내정보 불러오기
router.get("/getmyinfo", isLogin, getMyInfo);
// 게시글 삭제
router.get("/delete/:id", isLogin, deletePost);
// 글쓰기 기능
router.post("/insert", isLogin, insertPost);
// 수정 기능
router.post("/update/:id", isLogin, updatePost);
// 내정보 변경
router.post("/myinfoupdate/:id", isLogin, myInfoUpdate);
module.exports = router;
routers/signupRouter.js
회원가입 관련 라우팅 설정
const router = require("express").Router();
const { signUp } = require("../controllers/signUpController");
router.post("/", signUp);
module.exports = router;
app.js
프로젝트 서버 관련 설정
const express = require("express");
const session = require("express-session");
// env는 제일 위로 올려야함
const dot = require("dotenv").config();
const { sequelize } = require("./models");
const loginRouter = require("./routers/loginRouter");
const signupRouter = require("./routers/signupRouter");
const postRouter = require("./routers/postRouter");
const cors = require("cors");
const app = express();
app.use(express.urlencoded({ extended: false }));
// cors
// 다른 도메인에서 접근할 수 없도록 도메인 접근시 발생하는 보안 정책 설정
// 다른 도메인과 통신을 안전하게 유지 시키기 위한 보안정책
// Access-control-Allow-origin을 헤더에 포함하여 접근을 허용하고 응답
app.use(
cors({
// 도메인 허용 옵션
// 접근을 허용할 도메인 설정
// 여러개의 도메인을 허용하고 싶으면 배열의 형태로 넣어줌
origin: "http://127.0.0.1:5500",
credentials: true,
})
);
app.use(
session({
secret: process.env.SESSION_KEY,
resave: false,
saveUninitialized: false,
})
);
sequelize
.sync({
force: false,
})
.then((e) => {
console.log("database connect");
})
.catch((err) => {
console.error(err);
});
app.get("/", (req, res) => {
res.send("main");
});
app.use("/signUp", signupRouter);
app.use("/login", loginRouter);
app.use("/posts", postRouter);
app.listen(8080, () => {
console.log("Server On");
});
Frontend
img 폴더에는 데이터가 불러오기전에 잠시 화면을 가려줄 로딩화면관련 gif 파일이 존재. 실습에선 load-38.gif라는 파일 존재
insert.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>insert page</h2>
<form action="http://127.0.0.1:8080/posts/insert" method="post">
<label for="">title</label>
<input type="text" name="title" />
<label for="">content</label>
<input type="text" name="content" />
<button>insert</button>
</form>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>login page</h2>
<form action="http://127.0.0.1:8080/login" method="post">
<label for="">id</label>
<input type="text" name="user_id" />
<label for="">pw</label>
<input type="password" name="user_pw" />
<button>login</button>
</form>
</body>
</html>
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
// axios 라이브러리를 받아옴
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<style>
#isLoading {
background-color: white;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
}
#isLoading img {
width: 300px;
}
#isLoading.disable {
display: none;
}
</style>
</head>
<body>
<h2>main page</h2>
<div id="isLoading">
<img src="./img/load-38.gif" />
</div>
<div>
<div>
<span>name : </span>
<span id="user_name"></span>
</div>
<div>
<span>age : </span>
<span id="user_age"></span>
</div>
<div>
<a href="http://127.0.0.1:8080/posts/">posts</a>
<a href="http://127.0.0.1:5500/frontend/login.html">login</a>
<a href="http://127.0.0.1:5500/frontend/signUp.html">signup</a>
</div>
</div>
</body>
<script>
async function getAPI() {
try {
// withCredentials : true 브라우저상에서 쿠키를 서버로 전달 할 수 있는 옵션
// 기존에 실습했던 방식은 view engine 인 ejs를 활용해서 실습
// ejs를 활용하여 서버사이드 렌더링 방식으로 페이지를 로딩하면 서버측에서 로딩이 끝난 후에 화면을 띄워줌
// promise 기반인 axios를 활용하여 페이지를 구현하면 html 파일을 읽어들이고 비동기 방식으로 데이터를 요청하여 받아온 데이터를 body에 넣어줌
// 그렇기 때문에 데이터를 읽어 들이기 전까지는 데이터를 받아 출력하는 공간에 데이터가 없이 비어있어 그것을 가리기? 위해 로딩 gif를 넣어 데이터가 받아올때까지 기다림
const { data } = await axios.get("http://127.0.0.1:8080/login/view", {
withCredentials: true,
});
user_name.innerHTML = data.name;
user_age.innerHTML = data.age;
// 받아온 데이터를 body에 추가하고 로딩 gif를 끔
isLoading.classList.add("disable");
} catch (error) {
console.error(error);
}
}
getAPI();
</script>
</html>
myinfo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body></body>
<script>
async function getMydata() {
try {
const { data } = await axios.get(
"http://127.0.0.1:8080/posts/getmyinfo",
{ withCredentials: true }
);
document.body.innerHTML = `
<h2>myinfo page</h2>
<form action="http://127.0.0.1:8080/posts/myinfoupdate/${data.id}" method="post">
<label for="">name</label>
<input type="text" name="name" value="${data.name}" />
<label for="">age</label>
<input type="text" name="age" value="${data.age}" />
<button>change info</button>
</form>`;
} catch (error) {
console.error(error);
}
}
getMydata();
</script>
</html>
mypage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<h2>mypage</h2>
<a href="http://127.0.0.1:8080/posts/myinfo">myinfo</a>
<table id="mypageTable">
<tr>
<th>id</th>
<th>title</th>
<th>content</th>
<th>writer</th>
</tr>
</table>
</body>
<script>
async function getMyPost() {
try {
const { data } = await axios.get("http://127.0.0.1:8080/posts/mypost", {
withCredentials: true,
});
// posts.html의 for문과 비교
// 문자열을 조작하여 HTML을 생성하는 방식으로 브라우저가 한번에 처리할 수 있어 효율적이고 가독성이 좋음
// 문자열을 삽입하는 방식이기 때문에 해킹공격에 취약할수 있음(XSS)
// 별도의 라이브러리를 사용하여 보안에 신경써준다면 이방식이 더 좋아보임
// -> he 라이브러리 사용
// 예)
// const he = require("he");
//
// data.forEach((el) => {
// mypageTable.innerHTML += `<tr><td>${he.encode(el.id)}</td><td>${he.encode(el.title)}</td><td>${he.encode(el.content)}</td><td>${he.encode(el.writer)}</td></tr>`;
// });
// -> 특수문자를 이스케이핑하여 사용자가 입력한 데이터가 HTML 태그나 자바스크립트가 코드로 해석되지 않고 문자열로 처리되어 XSS 공격을 방지
// -> 다만 posts.html for문에 비해 느려질 수 있지만 사용자가 눈치챌만큼 느려지진 않음
data.forEach((el) => {
mypageTable.innerHTML += `<tr><td>${el.id}</td><td>${el.title}</td><td>${el.content}</td><td>${el.writer}</td></tr>`;
});
} catch (error) {
console.error(error);
}
}
getMyPost();
</script>
</html>
posts.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>posts</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<h2>posts page</h2>
<a href="http://127.0.0.1:8080/posts/insert">writing</a>
<a href="http://127.0.0.1:8080/posts/mypage">mypage</a>
<table id="postList">
<tr>
<th>title</th>
<th>content</th>
<th>writer</th>
</tr>
</table>
</body>
<script>
async function getPosts() {
try {
const { data } = await axios.get(
"http://127.0.0.1:8080/posts/viewAll",
{
withCredentials: true,
}
);
// mypage의 for문과 비교
// 두방식에 처리속도는 다르지 않음
// 이 방식은 DOM트리를 직접 다루는 것으로 상대적으로 더많은 계산이 필요
// 코드가 더 길고 복잡하지만 보안성은 좋음
data.forEach((el) => {
const createTr = document.createElement("tr");
const createTdTitle = document.createElement("td");
const createTdContent = document.createElement("td");
const createTdWriter = document.createElement("td");
const createTdUpdate = document.createElement("td");
const createTdDelete = document.createElement("td");
const updateAtag = document.createElement("a");
const deleteAtag = document.createElement("a");
updateAtag.setAttribute(
"href",
`http://127.0.0.1:8080/posts/update/${el.id}`
);
deleteAtag.setAttribute(
"href",
`http://127.0.0.1:8080/posts/delete/${el.id}`
);
updateAtag.innerHTML = "update";
deleteAtag.innerHTML = "delete";
createTdTitle.innerHTML = el.title;
createTdContent.innerHTML = el.content;
createTdWriter.innerHTML = el.writer;
createTr.append(createTdTitle);
createTr.append(createTdContent);
createTr.append(createTdWriter);
createTdUpdate.append(updateAtag);
createTdDelete.append(deleteAtag);
createTr.append(createTdUpdate);
createTr.append(createTdDelete);
postList.appendChild(createTr);
});
} catch (error) {
console.error(error);
}
}
getPosts();
</script>
</html>
signUp.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h2>signUp page</h2>
<form action="http://127.0.0.1:8080/signUp" method="post">
<label for="">name</label>
<input type="text" name="name" />
<label for="">age</label>
<input type="text" name="age" />
<label for="">user_id</label>
<input type="text" name="user_id" />
<label for="">user_pw</label>
<input type="password" name="user_pw" />
<button>signUp</button>
</form>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>update</title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body></body>
<script>
async function getSelectPost() {
try {
const { data } = await axios.get(
"http://127.0.0.1:8080/posts/selectPost/",
{
withCredentials: true,
}
);
document.body.innerHTML = `
<h2>update page</h2>
<form action="http://127.0.0.1:8080/posts/update/${data.id}" method="post">
<label for="">title</label>
<input type="text" name="title" value="${data.title}"/>
<label for="">content</label>
<input type="text" name="content" value="${data.content}"/>
<button>update</button>
</form>
`;
} catch (error) {
console.error(error);
}
}
getSelectPost();
</script>
</html>
'Nodejs' 카테고리의 다른 글
[NodeJs] tesseract를 활용한 OCR (0) | 2024.03.20 |
---|---|
[NodeJs] multer를 활용한 이미지 업로드 (0) | 2023.09.06 |
[NodeJs] WebSocket을 활용한 좌석예매 하기 (0) | 2023.09.06 |
[NodeJs] WebSocket을 활용한 채팅방 구현 (0) | 2023.08.31 |
[NodeJs] Sequelize 알아보기 (0) | 2023.08.30 |