데이터 베이스 팀플을 하면서 같은 팀원이 JS 및 백엔드 경험이 없어서 코드 읽는 것을 도와주는 걸 목적으로 글을 작성하였습니다.
사용 스택
- node js
- express
- swagger
주요 파일
app.js
- 백엔드에서 중심이 되는 파일로 middleware를 설정하고 관리한다 노드 서버의 entry point(서버로 들어오는 시작 지점)이다
routes
- 주소별로 라우터가 들어간다(네트워크에서 배우는 라우터랑 같은 느낌으로 생각하면 편하다) 서버로 들어오는 end point(서버로 들어오고 서버 안에서 최종적으로 도착하는 지점)를 지정한다
- router.(HTTP method) 구조로 해당 end point로 들어오게 되면 코드를 실행한다.
controllers
- end point로 들어온 요청을 토대로 로직을 작성하는 부분이다
models
- 실제로 DB에 접근하는 코드를 모은 것이다.
.env
- 환경변수를 저장해 놓은 파일
- 보안이 아주 중요시되므로 github에 올라가지 않음
- 저장되어 있는 목록으로는 데이터 베이스의 이름, 비밀번호, 포트 등이 있다
package.json
- 프로젝트(서버 x, 코드 o )의 정보를 나타내는 파일
- 어떤 라이브러리를 썼고 스크립트 정보가 담겨 있다
node_modules
- 라이브러리들의 집합
- 라이브러리를 설치하면 해당 폴더에 설치가 됨
- 라이브러리 코드를 사용할 때 이 폴에 있는 해당 라이브러리 안에서 가져와서 사용
- 개발하면서 직접적으로 사용할 일 없음
utils
- 다양한 유틸 함수(다른 파일과 관계없이 독립적으로 돌아가는 함수 = 의존 관계가 없음)가 들어있음
데이터 및 글의 흐름
- 프론트에서 서버에 요청
- app.js에 있는 미들웨어 수행
- app.js에서 경로에 맞는 router 이동(routes 폴더)
- 경로에 맞는 end point로 진입
- controller로 진입
- controller에서 로직을 수행하고 models로 들어가 DB에 접근
- controller의 결과를 return 함
- rouer에 결과에 맞는 응답을 프론트로 돌려줌
모든 문법은 JS를 기반으로 함.
APP.JS
app.use()를 이용하여 미들웨어를 등록할 수 있음
// app.js
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/userv1");
var postRouter = require("./routes/post");
var followRouter = require("./routes/follow");
var uploadRouter = require("./routes/upload");
// 파일을 import
app.use("/api/", indexRouter);
app.use("/api/v1/user", usersRouter);
app.use("/api/v1/post", postRouter);
app.use("/api/v1/follow", followRouter);
app.use("/api/v1/upload", uploadRouter);
// app.use("기본경로", "라우터 파일");
해당 코드는 express의 라우터를 지정해주는 코드이다
baseUrl + “기본 경로”+”최종경로” 형태가 있다고 하면 해당 코드는 기본 경로를 지정해준다.
Routes
게시글 작성 api를 예시로 들음.
//routes/post.js
const Post = require("../controllers/post.controller");
router.post("/", authJWT, async (req, res, next) => {
try {
const singlePost = await Post.writePost(req);
res.json(singlePost[0][0]);
} catch (error) {
return next(error);
}
});
// 해당 경로로 들어오면 authJWT를 수행하고 콜백함수로 넘어간다.
해당 코드는 router의 end point이다
[router.](<http://router.post>)HTTP_METHOD("최종경로","미들웨어","콜백함수") 형태로 이루어져 있다.
상위 파일(app.js)에서 app.use("/api/v1/post", postRouter); 로 기본 경로를 지정해 주었기 때문에 해당 코드는 baseUrl + api/v1/post/ 의 경로가 되는 것이다.
콜백 함수에서는 다음과 같은 형태로 이루어져 있는 걸 볼 수 있다
(req,res,next)=>{
..어쩌구 저쩌구...
}
req: 사용자가 보낸 http요청이 객체로 담겨 있다.
res: 사용자에게 보낼 http응답 객체를 지정한다.
요청과 응답에 초점을 두자
const singlePost = await Post.writePost(req);
함수의 매개변수를 req로 지정해서 실행하는 것을 알 수 있다.
Controllers
//controllers/post.controllers.js
// module.exports.(함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
// moduel.exports = (함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
module.exports.writePost = async (req) => {
let user = getUserFromReq(req); // 유저가 로그인이 되어 있다면 user에 id를 할당함
const { content, user_file_ids, location, hashtag, usertag } = req.body;
// 보낸 데이터를 꺼내는 단계
const insertedPost = await insertPost(user.id, content, location);
// DB에 Post를 삽입한다.
for (let i = 0; i < user_file_ids.length; i++) {
await insertImage(user.id, insertedPost[0].insertId, i, user_file_ids[i]);
}
// 이미지 개수만큼 DB에 이미지를 삽입한다.
for (let i = 0; i < hashtag.length; i++) {
await insertHashTag(insertedPost[0].insertId, hashtag[i]);
}
// 해시태그 개수만큼 DB에 해시태그를 삽입한다.
for (let i = 0; i < usertag.length; i++) {
await insertUserTag(insertedPost[0].insertId, usertag[i]);
}
// 유저태그 개수만큼 DB에 유저태그를 삽입한다.
const singlePost = await getSinglePost(insertedPost[0].insertId);
// 방금 삽입한 게시글을 가져온다
return singlePost;
//게시글을 반환한다.
};
- module.exports. vs moduel.exports = 차이점
// module.exports = 으로 사용을하면 지정한 하나의 함수만 다른 파일로 export할수 있다.
- // module.exports. 으로 사용을 하면 해당 함수를 호출할 시에 다음과 같은 구조 분해 할당 문법을 사용하거나 const { getUserFromReq } = require("../utils/awthJWT"); // 다음과 같이 임의로 정의해서 안에 있는 property로 접근해야 한다. const Post = require("../controllers/post.controller");
req데이터중 사용하는 몇 가지만 보겠다
- req.body: 클라이언트가 보낸 데이터가 객체로 담겨 있다 swagger에 작성해 둔 request body 양식 그대로 적용이 된다.
- req.params: url이 동적으로 변할 때 그 값을 가져온다/:userId처럼 경로에 :(이름)을 붙여주면 해당 위치의 경로는 동적으로 받겠다는 것이다. userId를 동적으로 받음으로써 유저에 맞게 api를 1대 1로 모두 지정하지 않고 다음과 같이 하나로 묶어서 사용할 수 있다.
- 해당 위치의 경로를 읽을 때는 req.params.(이름) 즉 위의 예시는 req.params.userId가 되는 것이다.
- //routes/post.js //해당 유저의 게시글을 전부 호출하는 api router.get("/list/:userId", authJWT, async (req, res, next) => { ..대충 코드.. });
Models
// models/post.model.js
const pool = require("../mysql");
const PostModel = {
getSinglePost: (postId) => {
const postSql = `select * from Post where id=?`;
return pool.query(postSql, postId);
},
insertPost: (userId, content, location) => {
const postSql = `insert into Post (user_id, content, location)values (?, ?, ?);`;
return pool.query(postSql, [userId, content, location]);
},
insertImage: (userId, postId, index, file_id) => {
const imageSql = `insert into Image (user_id, post_id, idx, user_file_id) values (?, ?, ?, ?);`;
return pool.query(imageSql, [userId, postId, index, file_id]);
},
insertHashTag: (post_id, name) => {
const likeSql = `insert into Hashtag (post_id, name) values (?, ?);`;
return pool.query(likeSql, [post_id, name]);
},
insertUserTag: (post_id, user_id) => {
const likeSql = `insert into Usertag (user_id, post_id) values (?, ?);`;
return pool.query(likeSql, [user_id, post_id]);
},
};
module.exports = PostModel;
쿼리문에 ? 를 넣어서 함수 파라미터로 넣듯이 sql문을 지정할수 있다.
pool.query(”sql문”,”파라미터 지정”)으로 DB에 접근할수 있으며 ?가 여러개일시 배열로 전달해 여러개의 매개변수를 지정할수 있다.
Controllers
다시 기존코드로 돌아와 보자
//controllers/post.controllers.js
// module.exports.(함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
// moduel.exports = (함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
module.exports.writePost = async (req) => {
let user = getUserFromReq(req); // 유저가 로그인이 되어 있다면 user에 id를 할당함
const { content, user_file_ids, location, hashtag, usertag } = req.body;
// 보낸 데이터를 꺼내는 단계
const insertedPost = await insertPost(user.id, content, location);
// DB에 Post를 삽입한다.
for (let i = 0; i < user_file_ids.length; i++) {
await insertImage(user.id, insertedPost[0].insertId, i, user_file_ids[i]);
}
// 이미지 개수만큼 DB에 이미지를 삽입한다.
for (let i = 0; i < hashtag.length; i++) {
await insertHashTag(insertedPost[0].insertId, hashtag[i]);
}
// 해시태그 개수만큼 DB에 해시태그를 삽입한다.
for (let i = 0; i < usertag.length; i++) {
await insertUserTag(insertedPost[0].insertId, usertag[i]);
}
// 유저태그 개수만큼 DB에 유저태그를 삽입한다.
const singlePost = await getSinglePost(insertedPost[0].insertId);
// 방금 삽입한 게시글을 가져온다
return singlePost;
//게시글을 반환한다.
};
DB에 접근하는 코드를 실행하면 항상 실행된 결과를 반환환다.
const insertedPost = await insertPost(user.id, content, location);
insertedPost를 보면 DB베이스에 넣은 결과가 객체로 되어있고 이중에서 0번쨰 index에 우리가 확인하고 싶은 결과가 있다
sql문의 update insert select문에 따라서 나오는 값들이 다르니 console.log로 직접 확인하는걸 추천한다.
- async/await
비동기는 코드를 실행할때 위에서 아래로 순차적으로 실행하는것이 아닌 나중에 따로 실행되는 코드를 뜻한다따라서 이를 해결하기 위해서 async/await이라는 문법을 사용한다 이렇게 쓰게 되면 위코드는 우리가 아는것처럼 insertPost가 실행되고 다음 줄이 실행이된다.module.exports.writePost = async (req) => { ..대충 코드.. const insertedPost = await insertPost(user.id, content, location); }
- 즉 위에서는 insertPost가 실행되고 다음 코드가 실행되는게 아닌 insertPost는 실행 시켜두고 다른 코드들이 실행이된다.
- js만의 특이한 개념인데 비동기 처리이다
Routes
게시글반환이되고 라우터로 다시 돌아왔다.
//routes/post.js
const Post = require("../controllers/post.controller");
router.post("/", authJWT, async (req, res, next) => {
try {
const singlePost = await Post.writePost(req);
res.json(singlePost[0][0]);
} catch (error) {
return next(error);
}
});
// 해당 경로로 들어오면 authJWT를 수행하고 콜백함수로 넘어간다.
singlePost 에는 return된 게시글이 들어있다.
res.josn(”json 데이터”)을 사용해 데이터를 클라이언트에 json형태로 보낼수 있다.
사용 스택
- node js
- express
- swagger
주요 파일
app.js
- 백엔드에서 중심이 되는 파일로 middleware를 설정하고 관리한다 노드 서버의 entry point(서버로 들어오는 시작 지점)이다
routes
- 주소별로 라우터가 들어간다(네트워크에서 배우는 라우터랑 같은 느낌으로 생각하면 편하다) 서버로 들어오는 end point(서버로 들어오고 서버안에서 최종적으로 도착하는 지점)를 지정한다
- router.(HTTP method) 구조로 해당 end point로 들어오게 되면 코드를 실행한다.
controllers
- end point로 들어온 요청을 토대로 로직을 작성하는 부분이다
models
- 실제로 DB에 접근하는 코드를 모은것이다.
.env
- 환경변수를 저장해 놓은 파일
- 보안이 아주 중요시 되므로 github에 올라가지 않음
- 저장되어 있는 목록으로는 데이터 베이스의 이름,비밀번호,포트 등이 있다
package.json
- 프로젝트(서버 x, 코드 o )의 정보를 나타내는 파일
- 어떤 라이브러리를 썼고 스크립트 정보가 담겨져 있다
node_modules
- 라이브러리들의 집합
- 라이브러리를 설치하면 해당 폴더에 설치가됨
- 라이브러리 코드를 사용할때 이 폴에 있는 해당 라이브러리안에서 가져와서 사용
- 개발하면서 직접적으로 사용할일 없음
utils
- 다양한 유틸함수(다른 파일 과 관계없이 독립적으로 돌아가는 함수 = 의존 관계가 없음)가 들어있음
데이터 및 글의 흐름
- 프론트에서 서버에 요청
- app.js에 있는 미들웨어 수행
- app.js에서 경로에 맞는 router 이동(routes 폴더)
- 경로에 맞는 end point로 진입
- controller로 진입
- controller에서 로직을 수행하고 models로 들어가 DB에 접근
- controller의 결과를 return함
- rouer에 결과에 맞는 응답을 프론트로 돌려줌
모든 문법은 JS를 기반으로 함.
APP.JS
app.use()를 이용하여 미들웨어를 등록할수 있음
// app.js
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/userv1");
var postRouter = require("./routes/post");
var followRouter = require("./routes/follow");
var uploadRouter = require("./routes/upload");
// 파일을 import
app.use("/api/", indexRouter);
app.use("/api/v1/user", usersRouter);
app.use("/api/v1/post", postRouter);
app.use("/api/v1/follow", followRouter);
app.use("/api/v1/upload", uploadRouter);
// app.use("기본경로", "라우터 파일");
해당 코드는 express의 라우터를 지정해주는 코드이다
baseUrl + “기본경로”+”최종경로” 형태가 있다고 하면 해당 코드는 기본경로를 지정해준다.
Routes
게시글 작성 api를 예시로 들음.
//routes/post.js
const Post = require("../controllers/post.controller");
router.post("/", authJWT, async (req, res, next) => {
try {
const singlePost = await Post.writePost(req);
res.json(singlePost[0][0]);
} catch (error) {
return next(error);
}
});
// 해당 경로로 들어오면 authJWT를 수행하고 콜백함수로 넘어간다.
해당 코드는 router의 end point이다
[router.](<http://router.post>)HTTP_METHOD("최종경로","미들웨어","콜백함수") 형태로 이루어져 있다.
상위 파일(app.js)에서 app.use("/api/v1/post", postRouter); 로 기본 경로를 지정해 주었기 때문에 해당 코드는 baseUrl + api/v1/post/ 의 경로가 되는 것이다.
콜백 함수에서는 다음과 같은 형태로 이루어져 있는걸 볼수 있다
(req,res,next)=>{
..어쩌구 저쩌구...
}
req: 사용자가 보낸 http요청이 객체로 담겨져 있다.
res: 사용자에게 보낼 http응답 객체를 지정한다.
요청과 응답에 초점을 두자
const singlePost = await Post.writePost(req);
함수의 매개변수를 req로 지정해서 실행 하는것을알수 있다.
Controllers
//controllers/post.controllers.js
// module.exports.(함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
// moduel.exports = (함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
module.exports.writePost = async (req) => {
let user = getUserFromReq(req); // 유저가 로그인이 되어 있다면 user에 id를 할당함
const { content, user_file_ids, location, hashtag, usertag } = req.body;
// 보낸 데이터를 꺼내는 단계
const insertedPost = await insertPost(user.id, content, location);
// DB에 Post를 삽입한다.
for (let i = 0; i < user_file_ids.length; i++) {
await insertImage(user.id, insertedPost[0].insertId, i, user_file_ids[i]);
}
// 이미지 개수만큼 DB에 이미지를 삽입한다.
for (let i = 0; i < hashtag.length; i++) {
await insertHashTag(insertedPost[0].insertId, hashtag[i]);
}
// 해시태그 개수만큼 DB에 해시태그를 삽입한다.
for (let i = 0; i < usertag.length; i++) {
await insertUserTag(insertedPost[0].insertId, usertag[i]);
}
// 유저태그 개수만큼 DB에 유저태그를 삽입한다.
const singlePost = await getSinglePost(insertedPost[0].insertId);
// 방금 삽입한 게시글을 가져온다
return singlePost;
//게시글을 반환한다.
};
- module.exports. vs moduel.exports = 차이점
// module.exports = 으로 사용을하면 지정한 하나의 함수만 다른 파일로 export할수 있다.
- // module.exports. 으로 사용을하면 해당 함수를 호출할시에 다음과 같은 구조분해 할당 문법을 사용하거나 const { getUserFromReq } = require("../utils/awthJWT"); // 다음과 같이 임의로 정의해서 안에 있는 property로 접근해야 한다. const Post = require("../controllers/post.controller");
req데이터중 사용하는 몇가지만 보겠다
- req.body: 클라이언트가 보낸 데이터가 객체로 담겨져 있다 swagger에 작성해 둔 request body 양식 그대로 적용이된다.
- req.params: url이 동적으로 변할때 그값을 가져온다/:userId처럼 경로에 :(이름) 을 붙여주면 해당 위치의 경로는 동적으로 받겠다는 것이다.userId를 동적으로 받음으로써 유저에 맞게 api를 1대1로 모두 지정하지 않고 다음과 같이 하나로 묶어서 사용할수 있다.
- 해당 위치의 경로를 읽을때는 req.params.(이름) 즉 위의 예시는 req.params.userId가 되는것이다.
- //routes/post.js //해당 유저의 게시글을 전부 호출하는 api router.get("/list/:userId", authJWT, async (req, res, next) => { ..대충 코드.. });
Models
// models/post.model.js
const pool = require("../mysql");
const PostModel = {
getSinglePost: (postId) => {
const postSql = `select * from Post where id=?`;
return pool.query(postSql, postId);
},
insertPost: (userId, content, location) => {
const postSql = `insert into Post (user_id, content, location)values (?, ?, ?);`;
return pool.query(postSql, [userId, content, location]);
},
insertImage: (userId, postId, index, file_id) => {
const imageSql = `insert into Image (user_id, post_id, idx, user_file_id) values (?, ?, ?, ?);`;
return pool.query(imageSql, [userId, postId, index, file_id]);
},
insertHashTag: (post_id, name) => {
const likeSql = `insert into Hashtag (post_id, name) values (?, ?);`;
return pool.query(likeSql, [post_id, name]);
},
insertUserTag: (post_id, user_id) => {
const likeSql = `insert into Usertag (user_id, post_id) values (?, ?);`;
return pool.query(likeSql, [user_id, post_id]);
},
};
module.exports = PostModel;
쿼리문에 ? 를 넣어서 함수 파라미터로 넣듯이 sql문을 지정할수 있다.
pool.query(”sql문”,”파라미터 지정”)으로 DB에 접근할수 있으며 ?가 여러개일시 배열로 전달해 여러개의 매개변수를 지정할수 있다.
Controllers
다시 기존코드로 돌아와 보자
//controllers/post.controllers.js
// module.exports.(함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
// moduel.exports = (함수,변수,객체이름) 으로 다른 파일에서 불러올수 있도록 설정한다
module.exports.writePost = async (req) => {
let user = getUserFromReq(req); // 유저가 로그인이 되어 있다면 user에 id를 할당함
const { content, user_file_ids, location, hashtag, usertag } = req.body;
// 보낸 데이터를 꺼내는 단계
const insertedPost = await insertPost(user.id, content, location);
// DB에 Post를 삽입한다.
for (let i = 0; i < user_file_ids.length; i++) {
await insertImage(user.id, insertedPost[0].insertId, i, user_file_ids[i]);
}
// 이미지 개수만큼 DB에 이미지를 삽입한다.
for (let i = 0; i < hashtag.length; i++) {
await insertHashTag(insertedPost[0].insertId, hashtag[i]);
}
// 해시태그 개수만큼 DB에 해시태그를 삽입한다.
for (let i = 0; i < usertag.length; i++) {
await insertUserTag(insertedPost[0].insertId, usertag[i]);
}
// 유저태그 개수만큼 DB에 유저태그를 삽입한다.
const singlePost = await getSinglePost(insertedPost[0].insertId);
// 방금 삽입한 게시글을 가져온다
return singlePost;
//게시글을 반환한다.
};
DB에 접근하는 코드를 실행하면 항상 실행된 결과를 반환환다.
const insertedPost = await insertPost(user.id, content, location);
insertedPost를 보면 DB베이스에 넣은 결과가 객체로 되어있고 이중에서 0번쨰 index에 우리가 확인하고 싶은 결과가 있다
sql문의 update insert select문에 따라서 나오는 값들이 다르니 console.log로 직접 확인하는걸 추천한다.
- async/await
비동기는 코드를 실행할때 위에서 아래로 순차적으로 실행하는것이 아닌 나중에 따로 실행되는 코드를 뜻한다따라서 이를 해결하기 위해서 async/await이라는 문법을 사용한다 이렇게 쓰게 되면 위코드는 우리가 아는것처럼 insertPost가 실행되고 다음 줄이 실행이된다.module.exports.writePost = async (req) => { ..대충 코드.. const insertedPost = await insertPost(user.id, content, location); }
- 즉 위에서는 insertPost가 실행되고 다음 코드가 실행되는게 아닌 insertPost는 실행 시켜두고 다른 코드들이 실행이된다.
- js만의 특이한 개념인데 비동기 처리이다
Routes
게시글반환이되고 라우터로 다시 돌아왔다.
//routes/post.js
const Post = require("../controllers/post.controller");
router.post("/", authJWT, async (req, res, next) => {
try {
const singlePost = await Post.writePost(req);
res.json(singlePost[0][0]);
} catch (error) {
return next(error);
}
});
// 해당 경로로 들어오면 authJWT를 수행하고 콜백함수로 넘어간다.
singlePost 에는 return된 게시글이 들어있다.
res.josn(”json 데이터”)을 사용해 데이터를 클라이언트에 json형태로 보낼수 있다.