김데이의 개발공부

[ TIL ] Day 52 - Multer 적용 방식 / 유효성 검사 validation 본문

코드잇 Node.js(BE) 부트 캠프/TIL (Today I Learn) 📑

[ TIL ] Day 52 - Multer 적용 방식 / 유효성 검사 validation

theday365 2025. 12. 8. 19:13
반응형

🗓️ 수업 일자 : 2025.12.8

✨ 오늘의 수업 평가 :  [ GOOD ] 머리가 터질 뻔했지만 배웠다 💥🤯📚

 

타입스크립트로 변환하면서 그 동안 묶혀두고 모른척(?) 했던 코드들을 보며

이제는 정말 수정을 해야 할 때가 된거 같아서 오늘 날 잡고 수정을 감행했다!

 

아직 validation 파일은 다 수정하지 못했지만, 

그래도 일단 도전 해 본거에 의의를 두며.. 

 

 

👩‍💻 [개인 프로젝트] 오늘 작업 내용 💻

-  이미지 업로드 Multer 업그레이드

 

📝  오늘 배운 내용  

- 파일 업로드 미들웨어 Multer 

- 유효성 검증 validation 파일 정리

 


1. 파일 업로드 미들웨어 Multer 

미들웨어 Multer 

브라우저가 파일을 보낼 때 multipart/form-data 형식으로 보냄
→ Express에서 처리 가능한 형식으로 파일 분석 후 req.file 또는 req.files 로 변환하여 사용 가능하게 제공

(Express 자체로는 브라우저가 보낸 파일 형식을 읽을 수 없음, 변환 과정이 꼭 필요함)

 

 

Multer의 주요 역할

  • 파일의 저장 위치 설정 : 로컬 / 메모리 / 외부 스토리지(S3 등) 등 어느 위치에 어떻게 저장 할 지 설정 가능
  • 파일 이름 & 경로에 대한 커스텀 가능 : 파일명이 충돌나지 않도록 변환을 하거나, 확장자 설정 작업등이 가능
  • 파일 종류 필터링 : 서버에 저장하고자 하는 파일 종류를 직접 선택 가능
  • 파일 갯수 설정 : 파일을 단일로 받을지, 여러 파일로 받을지 설정 가능

multer sample file

middleware/upload.js

import multer from 'multer';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';

// 파일 작업 상수 선언
const ALLOWED_MIME_TYPES: string[] = ['image/png', 'image/jpeg', 'image/jpg'];

// 5MB 파일 크기 제한
const FILE_SIZE_LIMIT: number = 5 * 1024 * 1024;

// 저장소 설정 
const PUBLIC_PATH = './files';

// Multer 저장소 설정
const storage = multer.diskStorage({
  destination(req, file, cb) {
    cb(null, PUBLIC_PATH);
  },
  filename(req, file, cb) {
    const ext = path.extname(file.originalname);
    const filename = `${uuidv4()}${ext}`;
    cb(null, filename);
  }
});

function imageFilter(req, file, cb) {
  if (!file.mimetype.startsWith('image/')) {
    return cb(new Error('이미지 파일만 업로드 가능합니다.'));
  }
  cb(null, true);
}

const upload = multer({
  storage,
  limits: { fileSize: FILE_SIZE_LIMIT },
  fileFilter: imageFileFilter,
})

// 프론트에서 업로드 필드명
export const uploadUserImage = upload.single('image');

 

 

Multer를 활용한 실전 예시 - User 등록 시 사용 한 이미지 파일을 multer로 처리하기

1) API 내부 포함 형식 : 유저 등록 요청 내에서 파일을 불러와 처리하는 방식, Multer를 파일 정리용 미들웨어로 사용

API 내부 포함 형식에 대한 다이어그램
API 내부 포함 형식에 대한 다이어그램

routers/userRouter.js 

import { Router } from 'express';
import { createUser } from '../controllers/userController.js';
import { uploadUserImage } from '../middleware/upload.js';

const router = Router();

// ⭐ 유저 생성 시 이미지 업로드 동시 처리 ⭐
// POST /api/users
router.post('/', uploadUserImage, createUser);
controllers/userController.js

import prisma from '../prismaClient.js';

// ⭐ 유저 생성 + 이미지 업로드 통합 버전 ⭐
export async function createUser(req, res, next) {
  try {
    const { name, phoneNumber } = req.body;

    if (!name || !phoneNumber) {
      return res.status(400).json({ message: "name과 phoneNumber는 필수값입니다." });
    }

    // 미들웨어 "uploadUserImage"의 multer가 성공하면 req.file 에 이미지 정보가 들어있음
    let imagePath = null;
    if (req.file) {
      imagePath = '/uploads/' + req.file.filename;
    }

    const newUser = await prisma.user.create({
      data: {
        name,
        phoneNumber: Number(phoneNumber),
        image: imagePath,
      },
    });

    return res.status(201).json(newUser);
  } catch (err) {
    next(err);
  }
}

 

 

2) 별도 모듈 형식 : 유저 등록 요청 내에서는 이미지 처리를 일절 하지 않고, 순수하게 등록만 진행
                             → 파일 변환 / 파일 관리 하는 모듈을 각각 만들어 유저 등록 요청은 "등록만" 할 수 있게 역할 분리, 완전 분업화

별도 모듈 형식에 대한 다이어그램
별도 모듈 형식에 대한 다이어그램

// routes/combinedRouter.js

import { Router } from 'express';
import { uploadUserImage } from '../middleware/upload.js';  // 기존 multer 설정 재사용

// multer와 유저 등록 요청 사이의 새로운 연결 모듈을 생성
import { createUserWithImage } from '../controllers/combinedController.js'; 

const router = Router();

// ⭐ 해당 포스트 요청으로 "이미지 업로드 후 유저 생성 작업으로 이미지 전달, 업로드 진행"
router.post('/create-user-with-image', uploadUserImage, createUserWithImage);

export default router;
controllers/combinedController.js
// ⭐ 업로드 한 이미지를 변환하여, 유저 생성 요청에 연결


import { createUser } from './userController.js';

export async function createUserWithImage(req, res, next) {
  try {
  
    // 1. multer 통과 후 req.file 존재함
    if (!req.file) {
      return res.status(400).json({ message: '이미지가 필요합니다.' });
    }

    // 2. 업로드된 파일 경로를 body.image 에 끼워넣기!
    req.body.image = '/uploads/' + req.file.filename;

    // ⭐ 3. 기존 createUser 요청 구문을 재사용
    return createUser(req, res, next);

  } catch (err) {
    next(err);
  }
}

 

 

2. 유효성 검증 validation 

유효성 검증 validation

  • Express/TS/Node 기반 서버에서 요청(req)의 값들이 유효한지 판단하는 로직을 모아둔 파일 또는 폴더를 칭하는 말로,
    validation 또는 validators, schemaValidator, dtoValidator 등으로 부름
  • 비지니스 로직의 경우 "요청"과 관련된 실제 작업만 진행해야 하므로,
    요청에 필요한 여러 값을 확인하는 작업을 validation으로 분리해서 관리 해야함

 

 

 

validation 파일의 주요 역할

  • req.body 값 검증 : 실제 요청에 사용 될 req.body의 각 값을 확인함.
                                 예시)  글쓰기에서는 title, content의 타입과 허용 범위(길이)를 체크,
                                           회원가입에서는 필수값이 모두 작성 되었는지 등을 확인 
  • req.query 값 검증 : 검색 / 필터 / 페이지네이션등 요청의 설정에 사용 될 각 값들을 확인 
                                  예시)  limit / offset 과 같은 값은 숫자로 사용 했는지, keyword 길이 제한 등 확인 
  • req.param 값 검증 : 삭제 / 수정 / 상세조회 등에서 사용되는 id 값이 제대로 작성 되었는지 확인
  • 이 외 요청 작업에 사용되는 기타 설정에 대해서도 모두 확인 진행

 

 

 

유효성 검증 라이브러리 종류 

 

1) Joi (조이)  가장 대중적인 라이브러리

  • Express, Nest 등 Node 기반에서 제일 인기 많음
  • 객체 스키마 정의가 직관적
  • 정규식·조건부 검증·커스텀 규칙 다 지원
  • 실무에서 압도적으로 많이 보임
예) const schema = Joi.object({ name: Joi.string().min(2).required() })

 

 

2) Yup (얍)

  • 프론트/백 모두에서 인기
  • React Hook Form과 궁합 좋음
  • 구조가 Joi와 비슷하지만 좀 더 프론트 친화적

 

3) Zod (조드)  TypeScript 신흥 강자

 

  • TS 개발자들이 요즘 제일 많이 씀
  • “타입 + 검증” 동시에 처리 (리턴 타입 자동 생성!!)
  • Next.js, Express, tRPC에서 대세

 

4) Superstruct

 

  • 빠르고 가볍고 심플
  • 함수형 스타일 좋아하는 사람들이 사용
  • Joi보다 문법이 훨씬 간단한 편

 

 

 

 

5) Express-validator

 

  • Express 전용 미들웨어
  • 비교적 오래된 스타일
  • req.body, req.params 바로 검증 가능
  • TS에서는 불편함 때문에 예전보다 사용량 줄어드는 중

 

6) class-validator (+ class-transformer)

 

  • 데코레이터 기반 "@IsString()", "@IsInt()" 같은 방식
  • DTO 구조 만들 때 깔끔함
  • Express에서도 쓸 수 있으나 셋업이 다소 번거롭긴 함
  • Nest.js에서는 사실상 표준

 

 

 

 


 

📃 내일은 뭘 배울까 🤔

- 코드레벨 아키텍쳐 : layered architecture 방식

반응형