Node.js에서 파일 업로드 로직을 만들다 보면, 로컬 파일을 읽어서 Buffer로 바꾸고, 다시 Blob이나 FormData에 넣는 식으로 흐름이 불필요하게 길어지는 경우가 많습니다. 특히 서버 사이드에서 외부 API로 파일을 중계 업로드할 때는 파일 표현을 몇 번이나 바꾸는지가 코드 복잡도와 메모리 사용량에 바로 영향을 줍니다.

fs.openAsBlob()은 이런 흐름을 꽤 깔끔하게 정리해 줍니다. 핵심은 파일을 바로 Blob으로 열어 Web API 계열인 fetch, FormData, Request와 자연스럽게 연결할 수 있다는 점입니다.

fs.openAsBlob가 필요한 이유

H3. 서버 코드에서도 Web API 업로드 흐름이 점점 많아졌다

최근 Node.js 코드는 브라우저와 비슷한 방식으로 fetch, FormData, Blob를 다루는 경우가 많습니다. 그런데 파일 입력만 여전히 readFile() 중심으로 처리하면 중간 변환이 늘어납니다.

import { readFile } from 'node:fs/promises';

const buffer = await readFile('report.csv');
const blob = new Blob([buffer], { type: 'text/csv' });

이 방식도 가능하지만, 파일을 한 번 더 메모리에 올리고 다시 감싸는 구조라서 코드 의도도 조금 흐려집니다.

H3. 파일을 Blob으로 직접 열면 업로드 코드의 역할이 분명해진다

fs.openAsBlob()을 쓰면 아래처럼 바로 시작할 수 있습니다.

import { openAsBlob } from 'node:fs';

const blob = await openAsBlob('report.csv', {
  type: 'text/csv',
});

이 패턴의 장점은 분명합니다.

  • 파일 → Blob 변환 단계를 단순화할 수 있다
  • FormData와 바로 연결하기 쉽다
  • 브라우저와 비슷한 업로드 코드를 서버에서도 유지하기 좋다

입력값이 파일 경로나 URL처럼 외부에서 들어오는 경우에는 Node.js URL.canParse 가이드: 사용자 입력 URL 검증을 예외 없이 안전하게 처리하는 법처럼 입력 검증을 먼저 분리하는 습관과 함께 가져가면 더 안전합니다.

fs.openAsBlob 기본 사용법

H3. 파일을 Blob으로 열고 바로 fetch에 넣는다

import { openAsBlob } from 'node:fs';

const blob = await openAsBlob('./avatar.png', {
  type: 'image/png',
});

await fetch('https://api.example.com/upload', {
  method: 'POST',
  body: blob,
  headers: {
    'content-type': blob.type,
  },
});

파일을 Buffer로 읽어 다시 감싸지 않아도 되기 때문에, 업로드 의도가 더 선명하게 드러납니다. 특히 단일 바이너리 파일을 그대로 전송하는 API에서는 이 패턴이 가장 단순합니다.

H3. FormData와 함께 멀티파트 업로드를 구성한다

import { openAsBlob } from 'node:fs';

const fileBlob = await openAsBlob('./report.csv', {
  type: 'text/csv',
});

const form = new FormData();
form.append('file', fileBlob, 'report.csv');
form.append('source', 'batch-job');

await fetch('https://api.example.com/import', {
  method: 'POST',
  body: form,
});

외부 스토리지, 사내 업로드 API, 백오피스 데이터 적재 엔드포인트처럼 멀티파트 폼 업로드가 필요한 상황에서 특히 잘 맞습니다. 브라우저와 거의 같은 패턴이라 프런트엔드 예제를 서버 코드로 옮겨 올 때도 덜 헷갈립니다.

실무에서 유용한 패턴

H3. 업로드 전 메타데이터 검증과 파일 선택을 분리한다

import { stat } from 'node:fs/promises';
import { openAsBlob } from 'node:fs';

async function createUploadBlob(path, type) {
  const info = await stat(path);

  if (info.size > 10 * 1024 * 1024) {
    throw new Error('파일 크기 제한을 초과했습니다.');
  }

  return openAsBlob(path, { type });
}

이 구조의 핵심은 열기 전에 크기, 확장자, 경로 정책을 먼저 확인한다는 점입니다. openAsBlob() 자체가 검증 정책을 대신해 주는 것은 아니므로, 파일 선택 로직은 따로 두는 편이 좋습니다.

H3. 취소 가능한 업로드 흐름과 결합한다

import { openAsBlob } from 'node:fs';

async function uploadFile(path, { signal }) {
  signal?.throwIfAborted();

  const blob = await openAsBlob(path, {
    type: 'application/pdf',
  });

  signal?.throwIfAborted();

  return fetch('https://api.example.com/upload', {
    method: 'POST',
    body: blob,
    signal,
    headers: {
      'content-type': blob.type,
    },
  });
}

대용량 업로드나 배치 중계 작업에서는 취소 응답성도 중요합니다. 이때는 Node.js AbortSignal.throwIfAborted 가이드: 취소 체크포인트를 안전하게 넣는 법처럼 업로드 전후 경계에 체크포인트를 넣는 패턴을 함께 쓰는 편이 좋습니다.

H3. 여러 파일을 순차 수집할 때는 메모리 흐름을 단순하게 유지한다

여러 파일을 한 번에 읽어 큰 배열로 묶기보다, 필요한 시점에 하나씩 Blob으로 열어 처리하는 편이 낫습니다. 반복 수집 패턴은 Node.js Array.fromAsync 가이드: 비동기 iterable을 배열로 안전하게 모으는 법처럼 언제 한꺼번에 모으고 언제 순차 처리할지를 구분하는 감각과도 이어집니다.

fs.openAsBlob를 쓸 때 주의할 점

H3. MIME 타입은 자동 판별이 아니라 직접 의도를 넣어 주는 편이 안전하다

openAsBlob(path, { type })type은 매우 중요합니다. 파일 확장자만 믿고 자동 추론할 것이라고 기대하기보다, 업로드 대상 API가 기대하는 MIME 타입을 명시적으로 넣는 편이 안전합니다.

예를 들어 CSV, PNG, PDF처럼 타입이 중요한 업로드는 아래처럼 분명하게 적는 것이 좋습니다.

  • text/csv
  • image/png
  • application/pdf

H3. 파일 존재 여부와 권한 오류는 별도로 다뤄야 한다

경로가 잘못됐거나 권한이 없으면 업로드 문제가 아니라 파일 접근 문제입니다. 이 둘을 같은 에러로 뭉개면 운영 때 원인 파악이 느려집니다.

try {
  const blob = await openAsBlob(path, { type: 'application/pdf' });
  return blob;
} catch (error) {
  throw new Error(`업로드 파일 준비 실패: ${path}`, {
    cause: error,
  });
}

이처럼 상위 문맥을 덧붙이는 방식은 Node.js Error cause 가이드: 감싼 에러에서도 원인을 잃지 않는 법과 같이 보면 더 잘 연결됩니다.

추천 적용 체크리스트

H3. 도입 전에 이 다섯 가지를 먼저 확인한다

  1. 업로드 대상이 Blob이나 FormData 기반인가?
  2. 파일 크기와 확장자 정책을 열기 전에 검증하는가?
  3. MIME 타입을 명시적으로 넣고 있는가?
  4. 취소와 타임아웃을 업로드 경계에서 확인하는가?
  5. 파일 접근 오류와 네트워크 업로드 오류를 구분하는가?

이 다섯 가지만 점검해도 파일 업로드 코드는 훨씬 읽기 쉬워집니다.

마무리

fs.openAsBlob()은 화려한 기능이라기보다, Node.js의 파일 시스템과 Web API 업로드 흐름을 더 자연스럽게 이어 주는 실용 도구에 가깝습니다. 특히 서버에서 외부 API로 파일을 전달하거나, 브라우저와 비슷한 업로드 코드를 유지하고 싶을 때 효과가 큽니다.

정리하면 이렇게 기억하면 됩니다.

  • 파일 업로드가 Blob 중심이라면 openAsBlob()이 잘 맞는다
  • 업로드 전 검증과 업로드 실행을 분리하면 운영이 쉬워진다
  • MIME 타입, 취소 처리, 파일 접근 오류 분리를 함께 챙겨야 한다

함께 보면 좋은 글