Session

현서·2025년 4월 24일

백엔드

목록 보기
6/18
post-thumbnail

2025.04.24 수업 & 과제 내용

➿ 세션(Session)이란?

"사용자와 서버 사이의 일시적인 연결 정보를 서버 쪽에 저장하는 것"

HTTP 통신은 원래 상태를 기억하지 못하는(stateless) 프로토콜이라서, 사용자가 누구인지 매번 요청마다 확인해야한다.

이때, 세션을 사용하면 서버가 사용자의 상태(로그인 정보, 장바구니 정보 등)를 기억할 수 있다.

➿17_session.js

const express = require("express");
const session = require("express-session");

const app = express();
app.use(express.json());

/*
    secret: 세션 ID 서명용 키
    resave: 매 요청시 세션 저장 여부
    saveUntinitialized: 초기 빈 세션을 저장 여부
    cookie.secure: HTTPS에서만 전송
*/

app.use(
  session({
    secret: "!@#$%^&*()",
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false },
  })
);

app.post("/login", (req, res) => {
  const { userid, password } = req.body;

  // 로그인 성공시!!
  req.session.user = { userid };
  res.send(`로그인 성공: ${userid}`);
});

app.get("/me", (req, res) => {
  if (req.session.user) {
    res.json(req.session.user);
  } else {
    res.status(401).send("로그인이 필요합니다.");
  }
});

app.get("/logout", (req, res) => {
  req.session.destroy(() => {
    res.send("로그아웃 되었습니다.");
  });
});

app.listen(3000, () => {
  console.log("서버 실행 중");
});

session은 로그인 성공시, 사용자의 로그인 정보를 담는다.
그래서 로그인 정보가 들어있니...?라고 물었을 때, if (req.session.user) 이면
로그인 정보를 출력한다.
만약 로그인 정보가 들어있지 않다면, "로그인이 필요합니다"를 출력한다.

🔍 req.session.destroy(...)
현재 사용자의 세션(session)을 완전히 삭제하는 함수이다.
서버에 저장되어 있던 로그인 정보나 사용자 관련 데이터가 지워진다.
그래서 이걸 실행하면 req.session.user 같은 정보도 모두 사라지기 때문에, 로그아웃이 된다.

일단 오늘 수업에서 한 폴더, 파일들은 이렇게 되어있다.

❇️ 먼저 127.0.0.9090/auth (회원가입/로그인) 코드 부터 살펴보자.

✨ app.mjs

import express from "express";
import session from "express-session";
import postsRouter from "./router/posts.mjs";
import authRouter from "./router/auth.mjs";

const app = express();

app.use(express.json());

app.use(
  session({
    secret: "!@#$%^&*()",
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false },
  })
);

app.use("/posts", postsRouter);
app.use("/auth", authRouter);

app.use((req, res, next) => {
  res.sendStatus(404);
});

app.listen(9090);

밑에 있는 코드들로

app.use("/posts", postsRouter);
app.use("/auth", authRouter);

127.0.0.9090/posts
127.0.0.9090/auth
이러한 경로로 실행이 된다.

✨ router 폴더 안에 있는 auth.mjs

import express from "express";
import * as authController from "../controller/auth.mjs";

const router = express.Router();

// 회원가입
// POST
// http://127.0.0.1:9090/auth/signup
router.post("/signup", authController.signup);

// 로그인
// POST
// http://127.0.0.1:9090/auth/login
router.post("/login", authController.login);

// 로그인 유지
// GET
// http://127.0.0.1:9090/auth/me
router.get("/me", authController.me);

// 로그아웃
// GET
// http://127.0.0.1:9090/auth/logout
router.get("/logout", authController.logout);

export default router;

✨ data 폴더 안에 있는 auth.mjs

let users = [
  {
    id: "1",
    userid: "apple",
    password: "1111",
    name: "김사과",
    email: "apple@apple.com",
    url: "https://randomuser.me/api/portraits/women/32.jpg",
  },
  {
    id: "2",
    userid: "banana",
    password: "2222",
    name: "반하나",
    email: "banana@banana.com",
    url: "https://randomuser.me/api/portraits/women/44.jpg",
  },
  {
    id: "3",
    userid: "orange",
    password: "3333",
    name: "오렌지",
    email: "orange@orange.com",
    url: "https://randomuser.me/api/portraits/men/11.jpg",
  },
  {
    id: "4",
    userid: "berry",
    password: "4444",
    name: "배애리",
    email: "orange@orange.com",
    url: "https://randomuser.me/api/portraits/women/52.jpg",
  },
  {
    id: "5",
    userid: "melon",
    password: "5555",
    name: "이메론",
    email: "orange@orange.com",
    url: "https://randomuser.me/api/portraits/men/29.jpg",
  },
];

// 회원가입 사용자 추가
export async function createUser(userid, password, name, email) {
  const user = {
    id: Date.now().toString(),
    userid,
    password,
    name,
    email,
    url: "https://randomuser.me/api/portraits/men/29.jpg",
  };
  users = [user, ...users];
  return users;
}

// 로그인
export async function login(userid, password) {
  const user = users.find(
    (user) => user.userid === userid && user.password === password
  );
  return user;
}

✨ controller 폴더 안에 있는 auth.mjs

import * as authRepository from "../data/auth.mjs";

// 회원가입을 진행하는 함수
export async function signup(req, res, next) {
  const { userid, password, name, email } = req.body;
  const users = await authRepository.createUser(userid, password, name, email);
  if (users) {
    res.status(201).json(users);
  }
}

// 로그인
export async function login(req, res, next) {
  const { userid, password } = req.body;
  const user = await authRepository.login(userid, password);
  if (user) {
    req.session.userid = userid;
    req.session.isLoggedIn = true;
    req.session.user = user;
    res.status(200).json(`${userid}님 로그인 완료!`);
  } else {
    res
      .status(404)
      .json({ message: `${userid}님 아이디 또는 비밀번호를 확인하세요` });
  }
}

// 로그인 유지
export async function me(req, res, next) {
  if (req.session.isLoggedIn) {
    res.json(req.session.user);
  } else {
    res.status(401).send("로그인이 필요합니다.");
  }
}

// 로그아웃
export async function logout(req, res, next) {
  req.session.destroy(() => {
    res.send("로그아웃 되었습니다.");
  });
}

로그인 유지 부분에 if(req.session.user) 이렇게만 해도 됨.
Postman으로 결과를 확인해보자.

로그아웃 이후, 로그인 정보를 확인하는 경우는 '로그인이 필요합니다'라고 잘 뜬다.

회원가입도 잘 된다.

❇️ 다음은 127.0.0.9090/posts (포스트 업로드) 코드를 살펴보자.

✨ router 폴더 안에 있는 posts.mjs

import express from "express";
import * as postController from "../controller/post.mjs";

const router = express.Router();

// 모든 포스트 가져오기
// 해당 아이디에 대한 포스트 가져오기
// GET
// http://127.0.0.1:9090/posts/
// http://127.0.0.1:9090/posts?userid=apple
router.get("/", postController.getPosts);

// 글번호에 대한 포스트 가져오기
// GET
// http://127.0.0.1:9090/posts/:id
router.get("/:id", postController.getPost);

// 포스트 쓰기
// POST
// http://127.0.0.1:9090/posts
// json 형태로 입력 후 저장
router.post("/", postController.createPost);

// 포스트 수정하기
// PUT
// http://127.0.0.1:9090/posts/:id
// json 형태로 입력 후 저장
router.put("/:id", postController.updatePost);

// 포스트 삭제하기
// DELETE
// http://127.0.0.1:9090/posts/:id
router.delete("/:id", postController.deletePost);

export default router;

✨ data 폴더 안에 있는 post.mjs

let posts = [
  {
    id: "1",
    name: "김사과",
    userid: "apple",
    text: "Node.js 배우는 중인데 Express 진짜 편하다! :로켓:",
    createdAt: Date.now().toString(),
    url: "https://randomuser.me/api/portraits/women/32.jpg",
  },
  {
    id: "2",
    name: "반하나",
    userid: "banana",
    text: "오늘의 커피 :커피:️ + 코딩 = 최고의 조합!",
    createdAt: Date.now().toString(),
    url: "https://randomuser.me/api/portraits/women/44.jpg",
  },
  {
    id: "3",
    name: "오렌지",
    userid: "orange",
    text: "Elasticsearch 연동 완료! 실시간 검색 API 짜릿해 :돋보기:",
    createdAt: Date.now().toString(),
    url: "https://randomuser.me/api/portraits/men/11.jpg",
  },
  {
    id: "4",
    name: "배애리",
    userid: "berry",
    text: "JavaScript 비동기 너무 어렵다... Promises, async/await, 뭐가 뭔지 :울음:",
    createdAt: Date.now().toString(),
    url: "https://randomuser.me/api/portraits/women/52.jpg",
  },
  {
    id: "5",
    name: "이메론",
    userid: "melon",
    text: "새 프로젝트 시작! Express + MongoDB + EJS 조합 좋아요 :전구:",
    createdAt: Date.now().toString(),
    url: "https://randomuser.me/api/portraits/men/29.jpg",
  },
];

// 모든 포스트를 리턴
export async function getAll() {
  return posts;
}

// 사용자 아이디(userid)에 대한 포스트를 리턴
// 조건을 만족하는 모든 요소를 배열로 리턴
export async function getAllByUserid(userid) {
  return posts.filter((post) => post.userid === userid);
}

// 글 번호(id)에 대한 포스트를 리턴
// 조건을 만족하는 첫 번째 요소 하나를 리턴
export async function getById(id) {
  return posts.find((post) => post.id === id);
}

// 포스트 작성
export async function create(userid, name, text) {
  const post = {
    id: Date.now().toString(),
    userid,
    name,
    text,
    createdAt: Date.now().toString(),
  };
  posts = [post, ...posts];
  return posts;
}

// 포스트 변경
export async function update(id, text) {
  const post = posts.find((post) => post.id === id);
  if (post) {
    post.text = text;
  }
  return post;
}

// 포스트 삭제
export async function remove(id) {
  posts = posts.filter((post) => post.id !== id);
}

✨ controller 폴더 안에 있는 post.mjs

import * as postRepository from "../data/post.mjs";

// 모든 포스트 / 해당 아이디에 대한 포스트를 가져오는 함수
export async function getPosts(req, res, next) {
  const userid = req.query.userid;
  const data = await (userid
    ? postRepository.getAllByUserid(userid)
    : postRepository.getAll());
  res.status(200).json(data);
}

// id를 받아 하나의 포스트를 가져오는 함수
export async function getPost(req, res, next) {
  const id = req.params.id;
  const post = await postRepository.getById(id);
  if (post) {
    res.status(200).json(post);
  } else {
    res.status(404).json({ message: `${id}의 포스트가 없습니다.` });
  }
}

// 포스트를 생성하는 함수
export async function createPost(req, res, next) {
  const { userid, name, text } = req.body;
  const posts = await postRepository.create(userid, name, text);
  res.status(201).json(posts);
}

// 포스트를 변경하는 함수
export async function updatePost(req, res, next) {
  const id = req.params.id;
  const text = req.body.text;
  const post = await postRepository.update(id, text);
  if (post) {
    res.status(201).json(post);
  } else {
    res.status(404).json({ message: `${id}의 포스트가 없습니다.` });
  }
}

// 포스트를 삭제하는 함수
export async function deletePost(req, res, next) {
  const id = req.params.id;
  await postRepository.remove(id);
  res.sendStatus(204);
}

마찬가지로 Postman에서 실행해 볼 수 있다.

😼 추가적인 내용 (Github branches)

그동안은 main branch에만 푸시하고 있었다.

하지만, 나뭇가지 뻗어나가듯이 다른 가지에 저장해놓으면,
뭔가 수정사항이나 고칠 것이 있을 때

다시 main branch로 돌아올 수 있어서 좋다.

😼 그리하여 현재 git branch를 확인하면,,,(cmd에 git branch하고 엔터)

😼 역시나 main이다.
음...나는 session-auth라는 다른 가지에 푸시할래! 라고 한다면...!

😼 이렇게 git checkout -b 하고 (내가 원하는 다른 가지 이름) 하면 된다.
나는 다른 가지 이름을 session-auth라고 할 거니까,
git checkout -b session-auth 라고 하고 엔터하면 됨.

그러면 캡쳐된 사진에서처럼 뭔가 session-auth하고 구름 아이콘 색깔이 다홍색? 주황색?으로 변경된 것을 확인할 수 있다.

profile
The light shines in the darkness.

0개의 댓글