위클리 페이퍼 9주차 정리

코드잇
이 글은 읽는데 약 6분이 걸립니다.

Q1. Express.js에서 미들웨어란 무엇인지 설명해 주세요.

Express.js에서 미들웨어란, 요청(request)과 응답(response) 사이에서 동작하는 함수를 의미한다.

즉, 클라이언트 → 서버로 들어온 요청을 처리하는 과정에서, 중간에 거쳐가는 처리 단계라고 생각하면 쉽다.

미들웨어의 특징
  • 일반적으로 req, res, next라는 세 개의 인자를 갖으며, 다음과 같이 구성된다.
function myMiddleware(req, res, next) {
  console.log("Middleware 실행됨");
  next(); // 다음 미들웨어로 제어를 넘김
}
  • 요청 → 응답 과정에서 여러 개의 미들웨어를 체인 형태로 연결할 수 있다.
const router = express.Router();
router.get(middleware1, middleware2, controller);
  • 요청(req) 가공, 인증/인가, 로그, 에러 핸들링(err 인자 필요), 정적 파일 제공 등 여러가지 역할을 수행할 수 있다.
import express from "express";
const app = express();

// 1. 로깅 미들웨어
function logger(req, res, next) {
  console.log(`${req.method} ${req.url}`);
  next(); // 다음으로 제어 넘기기
}

// 2. 인증 미들웨어
function auth(req, res, next) {
  if (!req.headers.authorization) {
    return res.status(401).json({ message: "Unauthorized" });
  }
  next();
}

// 3. 라우터
app.get("/profile", auth, (req, res) => {
  res.json({ message: "Welcome to profile!" });
});

// 전역 미들웨어 등록
app.use(logger);

// 에러 핸들링 미들웨어 (4개의 인자 필요!)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: "Something broke!" });
});

app.listen(3000, () => console.log("Server running..."));

Q2. 백엔드 서버에서 이미지 업로드를 구현하는 다양한 방식에 대해 설명해 주세요.

백엔드 서버에서 이미지 업로드를 구현하는 방식은 프로젝트의 요구사항(성능, 확장성, 비용, 보안 등)에 따라 달라지며, 크게 나누면 저장 위치(로컬 vs 외부)와 전송 방식에 따라 여러 전략이 있다.

1. 로컬 저장소(Local Storage)에 저장

가장 단순한 방식으로, 클라이언트가 업로드한 이미지를 서버의 파일시스템에 저장하는 방법이다. 구현 방법은 다음과 같다.

  • Multer 같은 라이브러리를 이용해 multipart/form-data 요청을 파싱
  • dest 또는 diskStorage 옵션을 이용해 서버 디스크에 저장
  • DB에는 이미지 파일 경로(Path)만 저장
import multer from "multer";
const upload = multer({ dest: "uploads/" });

app.post("/upload", upload.single("image"), (req, res) => {
  res.json({ file: req.file });
});

간단하고 빠르게 구현 가능한 장점이 있으나, 서버 확장(스케일 아웃) 시 이미지 동기화 문제가 발생할 수 있으며, 서버 디스크 용량에 의존한다는 단점이 있다.

2. 데이터베이스에 BLOB(Binary Large Object) 형태로 저장

이미지를 DB 칼럼에 직접 저장하는 방식이며, 구현 방법은 다음과 같다.

  • 이미지 파일을 Buffer 형태로 받아 DB 칼럼(bytea, BLOB)에 저장
  • Prisma ORM으로 다음과 같이 표현할 수 있다.
await prisma.image.create({
  data: {
    filename: req.file.originalname,
    data: req.file.buffer, // Buffer 형태로 DB에 저장
  }
});

DB 백업/복구만으로 파일 일관성을 보장할 수 있으나, 성능 저하가 발생할 수 있고 DB 용량에 부담되는 점, 그리고 대규모 서비스엔 비효율적이라는 단점이 있다.

3. 외부 클라우드 스토리지에 저장

이미지를 서버가 아니라 클라우드 스토리지에 저장하는 방식으로, 제공 서비스로는 AWS S3, Google Cloud Storage, Azure Blob Storage 등이 있다.

방식 1, 2에서는 클라이언트 → 서버로 데이터가 전송된다. 그러나 이 방식에서는 일반적으로 서버가 사전 서명 URL(Presigned URL)을 발급하고, 클라이언트 → 클라우드 스토리지로 바로 전송되도록 하여 서버에 부담이 적다는 특징이 있다.

AWS S3 서비스를 예로 들면 다음과 같다.

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({ region: "ap-northeast-2" });

async function uploadToS3(file) {
  const command = new PutObjectCommand({
    Bucket: "my-bucket",
    Key: file.originalname,
    Body: file.buffer,
    ContentType: file.mimetype,
  });
  await s3.send(command);
}

이 방식은 확장성이 뛰어나고 CDN 연계가 용이하지만, 클라우드 사용 비용이 발생하고 초기 설정이 복잡하다는 단점이 있다.

이외에도 업로드된 이미지를 CDN(Content Delivery Network)을 통해 배포하는 방법, 대용량 파일 업르도 시 Chunk 단위로 나누어 업로드하는 방식, 이미지 데이터를 Base64 문자열로 변환하여 JSON body에 담아 전송하는 방법 등이 있다.


위 업로드 방식들 중 어느 방식을 쓰는 게 좋을지는, 진행하는 프로젝트의 규모나 목적에 따라 달라지는데, 간단히 정리하면 다음과 같다.

  • 개인 프로젝트 / 소규모 서비스 → 로컬 저장소 + DB에 경로 저장
  • 중소 규모 / 여러 서버 운영 → AWS S3 같은 외부 스토리지
  • 대규모 글로벌 서비스 → S3 + CDN + Presigned URL 방식
  • 보안이 중요한 서비스 → Presigned URL + 인증 미들웨어