[TIL/크래프톤 정글] DAY 100

배재준·2025년 6월 18일

크래프톤 정글 - TIL

목록 보기
92/93
post-thumbnail

2025.06.17

TIL(TODAY I LEARN)


  • 오늘한 내용 : 게시글/댓글 작성 및 회원가입·로그인 api 구현

  • WEEK 14 : 실력 다지기 - 백엔드 & DB


구현

DB 스키마 구성

모델필드명타입설명
UserusernameString고유 아이디 (trim + unique)
passwordString비밀번호 (plain, 추후 해싱)
PosttitleString게시글 제목
contentString게시글 본문
authorString작성자 이름 (username)
commentsArray댓글 배열 (중첩 문서)
CommentauthorString댓글 작성자
contentString댓글 내용
createdAtDate댓글 생성 시간

models/Post.js

// models/Post.js
const mongoose = require("mongoose");

// 댓글 스키마 (Post에 포함됨)
const commentSchema = new mongoose.Schema({
  author: { type: String, required: true }, // 댓글 작성자
  content: { type: String, required: true }, // 댓글 내용
  createdAt: { type: Date, default: Date.now }, // 생성 시간
});

// 게시글 스키마
const postSchema = new mongoose.Schema(
  {
    title: { type: String, required: true },
    content: { type: String, required: true },
    author: { type: String, required: true }, // 게시글 작성자
    comments: [commentSchema], // 중첩된 댓글 배열
  },
  { timestamps: true }
);

module.exports = mongoose.model("Post", postSchema);

models/User.js

// models/User.js
const mongoose = require("mongoose");

const userSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      unique: true, // 중복 방지
      trim: true, // 공백 자동 제거
    },
    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true } // createdAt, updatedAt 자동 생성
);

module.exports = mongoose.model("User", userSchema);

controllers/userController.js

// controllers/userController.js
const User = require("../models/User");

// 회원가입
exports.signup = async (req, res) => {
  const { username, password } = req.body;

  try {
    const existing = await User.findOne({ username });
    if (existing) {
      return res.status(409).json({ message: "이미 존재하는 사용자입니다." });
    }

    const newUser = new User({ username, password }); // 나중에 해싱 필요
    await newUser.save();

    res.status(201).json({ message: "회원가입 성공", username });
  } catch (err) {
    res.status(500).json({ message: "회원가입 실패", error: err.message });
  }
};

// 로그인
exports.login = async (req, res) => {
  const { username, password } = req.body;

  try {
    const user = await User.findOne({ username });

    if (!user || user.password !== password) {
      return res
        .status(401)
        .json({ message: "아이디 또는 비밀번호가 틀렸습니다." });
    }

    // 로그인 성공 → 토큰 발급은 나중에
    res.json({ message: "로그인 성공", username });
  } catch (err) {
    res.status(500).json({ message: "로그인 실패", error: err.message });
  }
};
  • 회원가입 (signup)
  • 로그인 (login)

controllers/postController.js

const Post = require("../models/Post");

// 전체 게시글 조회
exports.getAllPosts = async (req, res) => {
  try {
    const posts = await Post.find().sort({ createdAt: -1 }); // 최신순 정렬
    res.json(posts);
  } catch (err) {
    res.status(500).json({ message: "게시글 조회 실패", error: err.message });
  }
};

// 게시글 작성
exports.createPost = async (req, res) => {
  const { title, content, author } = req.body;

  try {
    const newPost = new Post({ title, content, author });
    const savedPost = await newPost.save();
    res.status(201).json(savedPost);
  } catch (err) {
    res.status(500).json({ message: "게시글 작성 실패", error: err.message });
  }
};

// 게시글 상세 조회
exports.getPostById = async (req, res) => {
  const { id } = req.params;

  try {
    const post = await Post.findById(id);
    if (!post)
      return res.status(404).json({ message: "게시글을 찾을 수 없습니다." });
    res.json(post);
  } catch (err) {
    res.status(500).json({ message: "게시글 조회 실패", error: err.message });
  }
};

// 게시글 삭제
exports.deletePost = async (req, res) => {
  const { id } = req.params;

  try {
    await Post.findByIdAndDelete(id);
    res.status(204).send(); // No Content
  } catch (err) {
    res.status(500).json({ error: "게시글 삭제 실패" });
  }
};

// 댓글 추가
exports.addComment = async (req, res) => {
  const { id } = req.params; // 게시글 ID
  const { author, content } = req.body;

  try {
    const post = await Post.findById(id);
    if (!post)
      return res.status(404).json({ message: "게시글을 찾을 수 없습니다." });

    const newComment = { author, content, createdAt: new Date() };
    post.comments.push(newComment);
    await post.save();

    res.status(201).json(newComment);
  } catch (err) {
    res.status(500).json({ message: "댓글 등록 실패", error: err.message });
  }
};

// 댓글 삭제
exports.deleteComment = async (req, res) => {
  const { id: postId, commentId } = req.params;

  try {
    const post = await Post.findById(postId);
    if (!post)
      return res.status(404).json({ message: "게시글을 찾을 수 없습니다." });

    // 댓글 필터링
    const originalCount = post.comments.length;
    post.comments = post.comments.filter((c) => c._id.toString() !== commentId);

    if (post.comments.length === originalCount) {
      return res.status(404).json({ message: "댓글을 찾을 수 없습니다." });
    }

    await post.save();
    res.status(204).send(); // No Content
  } catch (err) {
    console.error("댓글 삭제 실패:", err);
    res.status(500).json({ message: "댓글 삭제 실패" });
  }
};
  • 게시글 전체 조회
  • 게시글 작성
  • 게시글 상세 조회
  • 댓글 추가

routes/post.js

// routes/posts.js
const express = require("express");
const router = express.Router();
const postController = require("../controllers/postController");

// 전체 게시글 조회
router.get("/", postController.getAllPosts);

// 게시글 작성
router.post("/", postController.createPost);

// 게시글 상세 조회
router.get("/:id", postController.getPostById);

// 게시글 삭제S
router.delete("/:id", postController.deletePost);

// 댓글 추가
router.post("/:id/comments", postController.addComment);

// 댓글 삭제
router.delete("/:id/comments/:commentId", postController.deleteComment);

module.exports = router;

routes/users.js

const express = require("express");
const router = express.Router();
const userController = require("../controllers/userController");

// 회원 가입
router.post("/signup", userController.signup);

// 로그인
router.post("/login", userController.login);

module.exports = router;

index.js

// backend/index.js

// .env 파일을 불러와서 process.env로 사용할 수 있게 함
require("dotenv").config();

const mongoose = require("mongoose");
const express = require("express");
const cors = require("cors");

const app = express();

// 환경 변수에서 PORT 불러오고, 없으면 5000 사용
const PORT = process.env.PORT || 5000;

// JSON 요청 파싱
app.use(
  cors({
    origin: "http://localhost:5173", // 프론트 주소
    credentials: true,
  })
);
app.use(express.json());

// DB 연결
mongoose
  .connect(process.env.MONGO_URI)
  .then(() => console.log("✅ MongoDB connected"))
  .catch((err) => console.error("❌ MongoDB connection error:", err));

// 라우터 불러오기
const userRoutes = require("./routes/users");
const postRouter = require("./routes/posts");

app.use("/api/users", userRoutes);
app.use("/api/posts", postRouter);

// 서버 시작
app.listen(PORT, () => {
  console.log(`✅ Backend running at http://localhost:${PORT}`);
});

1개의 댓글

comment-user-thumbnail
2025년 8월 26일

Bloodmoney Game is a dark-humor / horror clicker game (short indie experience) in which you need to raise $25,000 for an operation by repeatedly clicking to get money — but the upgrades and choices push the game into disturbing, violent, and fourth-wall-breaking territory.

답글 달기