두피만
FE 던지기
두피만
전체 방문자
오늘
어제
  • 분류 전체보기
    • 기술 경험
    • 기술에 대한 생각들
    • 회고록
    • 프로그라피
    • 구 게시글
      • 백엔드 찍먹
      • 삽질
      • React
      • 라이브러리 소개
      • Breaking 프로젝트

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • query string
  • 리엑트 노드버드
  • Redux
  • 프로그라피
  • 전역상태
  • JS로 React 구현하기
  • 삽질
  • 프론트엔드
  • 상태관리
  • 셀프다이닝
  • SVG
  • 노드버드
  • react-query
  • Modal
  • 프로그라피 React
  • 서비스 발전 과정
  • 프로그라피 8기
  • typescript
  • react
  • 아이디어 도출

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
두피만

FE 던지기

백엔드 코드를 읽어보자
구 게시글/백엔드 찍먹

백엔드 코드를 읽어보자

2022. 11. 25. 23:40

데이터 베이스 팀플을 하면서  같은 팀원이 JS 및 백엔드 경험이 없어서 코드 읽는 것을 도와주는 걸 목적으로 글을 작성하였습니다.

사용 스택

  1. node js
  2. express
  3. 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

  • 다양한 유틸 함수(다른 파일과 관계없이 독립적으로 돌아가는 함수 = 의존 관계가 없음)가 들어있음

데이터 및 글의 흐름

  1. 프론트에서 서버에 요청
  2. app.js에 있는 미들웨어 수행
  3. app.js에서 경로에 맞는 router 이동(routes 폴더)
  4. 경로에 맞는 end point로 진입
  5. controller로 진입
  6. controller에서 로직을 수행하고 models로 들어가 DB에 접근
  7. controller의 결과를 return 함
  8. 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
    module.exports.writePost = async (req) => {
    ..대충 코드..
    const insertedPost = await insertPost(user.id, content, location);
    }
    
    
    비동기는 코드를 실행할때 위에서 아래로 순차적으로 실행하는것이 아닌 나중에 따로 실행되는 코드를 뜻한다따라서 이를 해결하기 위해서 async/await이라는 문법을 사용한다 이렇게 쓰게 되면 위코드는 우리가 아는것처럼 insertPost가 실행되고 다음 줄이 실행이된다.
  • 즉 위에서는 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형태로 보낼수 있다.

사용 스택

  1. node js
  2. express
  3. 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

  • 다양한 유틸함수(다른 파일 과 관계없이 독립적으로 돌아가는 함수 = 의존 관계가 없음)가 들어있음

데이터 및 글의 흐름

  1. 프론트에서 서버에 요청
  2. app.js에 있는 미들웨어 수행
  3. app.js에서 경로에 맞는 router 이동(routes 폴더)
  4. 경로에 맞는 end point로 진입
  5. controller로 진입
  6. controller에서 로직을 수행하고 models로 들어가 DB에 접근
  7. controller의 결과를 return함
  8. 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
    module.exports.writePost = async (req) => {
    ..대충 코드..
    const insertedPost = await insertPost(user.id, content, location);
    }
    
    
    비동기는 코드를 실행할때 위에서 아래로 순차적으로 실행하는것이 아닌 나중에 따로 실행되는 코드를 뜻한다따라서 이를 해결하기 위해서 async/await이라는 문법을 사용한다 이렇게 쓰게 되면 위코드는 우리가 아는것처럼 insertPost가 실행되고 다음 줄이 실행이된다.
  • 즉 위에서는 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형태로 보낼수 있다.

    두피만
    두피만

    티스토리툴바