본문 바로가기
JavaScript/Node.js

Node) 익스프레스로 SNS 서비스 만들기 - multer 패키지로 이미지 업로드 구현하기

by 박채니 2023. 1. 2.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[Node.js 교과서]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

익스프레스로 SNS 서비스 만들기

 

multer 패키지로 이미지 업로드 구현하기

 

패키지 설치

$ yarn add multer

input 태그를 통해 이미지를 선택할 때 바로 업로드를 진행하고, 업로드된 사진 주소를 다시 클라이언트에 알릴 것입니다.

또한, 게시글을 저장할 때는 데이터베이스에 직접 이미지 데이터를 넣는 대신 이미지 경로만 저장하며 이미지는 서버 디스크에 저장됩니다.

 

routes/post.ts

import express from "express";
import multer from "multer";
import path from "path";
import fs from "fs";
import { isLoggedIn } from "../middlewares";
import { afterUploadImage, uploadPost } from "../controllers/post";

const router = express.Router();

try {
  fs.readFileSync("uploads");
} catch (error) {
  console.error("uploads 폴더가 없어 uploads 폴더를 생성합니다.");
  fs.mkdirSync("uploads");
}

const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, cb) {
      cb(null, "uploads/");
    },
    filename(req, file, cb) {
      const ext = path.extname(file.originalname);
      cb(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: { fileSize: 5 * 1024 * 1024 },
});

// POST /post/img
router.post("/img", isLoggedIn, upload.single("img"), afterUploadImage);

// POST /post
const upload2 = multer();
router.post("/", isLoggedIn, upload.none(), uploadPost);

export default router;

 

controllers/post.ts

import Post from "../models/post";
import Hashtag from "../models/hashtag";
import { NextFunction, Request, Response } from "express";

const afterUploadImage = (req: Request, res: Response) => {
  console.log(req.file);
  res.json({ url: `/img/${req.file!.filename}` });
};

const uploadPost = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const post = await Post.create({
      content: req.body.content,
      img: req.body.url,
      UserId: req.user!.id,
    });
    const hashtags = req.body.content.match(/#[^\s#]*/g);
    if (hashtags) {
      const result = await Promise.all(
        hashtags.map((tag: string) => {
          return Hashtag.findOrCreate({
            where: { title: tag.slice(1).toLowerCase() },
          });
        })
      );
      await post.addHashtags(result.map((r) => r[0]));
    }
    res.redirect("/");
  } catch (error) {
    console.error(error);
    next(error);
  }
};

export { afterUploadImage, uploadPost };

POST /post/img 라우터에서는 이미지 하나를 업로드 받은 뒤 이미지의 저장 경로를 클라이언트로 응답합니다.

static 미들웨어가 /img 경로의 정적 파일을 제공하므로 클라이언트에서 업로드한 이미지에 접근할 수 있습니다.

 

POST /post 라우터는 게시글 업로드를 처리하는 라우터입니다.

만일 이전 라우터에서 이미지를 업로드 했다면, 이미지 주소도 req.body.url로 전송됩니다. (이미지 주소가 넘어온 것이므로 none 메소드 사용)

게시글을 데이터베이스에 저장한 후 게시글 내용에서 해시태그를 정규표현식으로 추출해내었습니다.

추출할 해시태그는 #를 떼고 소문자로 변경한 후 저장하였으며, findOrCreate 메소드를 이용해 데이터베이스에 해시태그가 존재하면 가져오고, 존재하지 않으면 생성해주었습니다.

결괏값으로 [모델, 생성여부]를 리턴해주기 때문에, 모델만 추출하여 post.addHashtags 메소드로 게시글과 연결하였습니다.

 

controllers/page.ts

import { NextFunction, Request, Response } from "express";
import User from "../models/user";
import Post from "../models/post";

export const renderProfile = (req: Request, res: Response) => {
  res.render("profile", { title: "내 정보 - NodeBird" });
};

export const renderJoin = (req: Request, res: Response) => {
  res.render("join", { title: "회원 가입 - NodeBird" });
};

export const renderMain = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const post = await Post.findAll({
      include: {
        model: User,
        attributes: ["id", "nick"],
      },
      order: [["createdAt", "DESC"]],
    });

    res.render("main", {
      title: "NodeBird",
      twits: post,
    });
  } catch (error) {
    console.error(error);
    next(error);
  }
};