개발 기술 · 더 알아보기 · 직접 체험형

사진 첨부 글쓰기, 파일과 게시글은 서버에 어떻게 저장될까요?

앱의 사진 미리보기에서 파일 전송·서버 저장·첨부 번호 응답·게시글 연결과 별도 조회까지, 사진 한 장이 글에 붙는 실제 구현 과정을 살펴봅니다.

사진 전송과 첨부 번호글 저장과 연결실패·재시도·별도 조회
앱에서 사진과 제목을 보내고, 서버가 사진을 저장해 글에 연결한 뒤, 저장된 글과 사진을 다시 확인하는 세 단계.

작동으로 살펴보는 개발 과정

사진 전송과 게시글 저장을 비교하세요

사진 선택 → 전송 → 글 등록 → 재조회 순서로 누르고, 파일·게시글·첨부 연결 개수를 비교하세요.

파일 전송 → 첨부 번호 응답 → 글과 연결 → 목록·상세 조회

시연만 크게 보기 ↗

앱과 서버를 연결하는 구현 과정

사진 한 장이 저장된 글에 붙기까지

사진을 첨부하고 등록을 누르면 글 한 건이 만들어집니다. 하지만 앱 안에서는 사진을 고르는 일, 서버에 파일을 보내는 일, 그 파일을 글에 연결하는 일이 서로 다른 단계로 이어집니다. 사진이 화면에 보인다고 서버 저장이나 글 등록까지 끝난 것은 아닙니다.

데브크래프트는 앱이 보낸 파일과 글을 서버에서 각각 확인하고, 저장한 첨부 번호로 두 정보를 연결하도록 구현합니다. 이 글에서는 사진 한 장을 붙이는 작업 기록을 통해, 정상 저장과 실패·재시도에서 무엇이 달라지는지 실제 요청과 저장 결과로 살펴봅니다.

구현 과정 01

사진을 고르면 앱 안에서 먼저 미리보기를 보여줍니다

사용자가 사진을 고르면 앱은 선택한 파일로 미리보기를 만듭니다. 제목과 사진을 확인하고 바꿀 수 있는 단계입니다. 아직 서버에 전송하지 않았다면 다른 기기나 저장 목록에서 이 사진을 읽을 수는 없습니다.

이때 앱은 파일 이름·크기와 선택 여부를 관리합니다. 허용되지 않는 형식이나 너무 큰 파일을 미리 안내하면 사용자가 기다린 뒤 실패하는 일을 줄일 수 있습니다. 다만 앱의 검사를 통과했다는 이유로 서버의 검사를 생략하지는 않습니다.

시연에서는 준비된 PNG 사진을 선택합니다. 사진이 보인 직후 오른쪽의 원본 파일·게시글·첨부 연결은 모두 0개입니다. 앱의 미리보기와 서버의 저장 결과를 나란히 보면 두 상태가 구분됩니다.

앱에서 연결하는 일

선택한 사진과 제목을 보여주고, 아직 전송 전이라는 상태를 관리합니다.

서버에서 구현하는 일

파일 전송 요청이 오기 전에는 사진이나 글을 저장하지 않습니다.

요청 값과 구현 기준 자세히 보기
// 선택한 파일로 앱 안의 미리보기 만들기
const previewUrl = URL.createObjectURL(file);
image.src = previewUrl;

// 이 코드만으로 서버에 파일이 저장되지는 않습니다.

구현 과정 02

앱이 파일을 보내면 서버가 형식·크기·내용을 확인합니다

앱은 선택한 파일을 파일 전송 API로 보냅니다. 전송할 때는 파일 바이트와 형식 등 필요한 정보를 묶습니다. 시연은 FormData에 파일 한 개를 담아 POST /api/attachments로 전송합니다.

서버는 현재 요청의 사용 범위를 확인하고, 받을 파일 개수·용량·허용 형식을 검사합니다. 파일 이름을 .png로 바꾸거나 전송 형식을 image/png로 적었다고 실제 이미지가 되는 것은 아닙니다. 파일 내용을 함께 확인해야 합니다.

이 시연의 허용 범위는 8MB 이하의 8비트 RGB/RGBA, 비인터레이스 PNG 한 장입니다. PNG 헤더와 청크 무결성, 압축 데이터와 이미지 크기를 검사합니다. 실제 서비스의 허용 형식과 파일 검사 기준은 업로드 목적에 맞게 정합니다.

‘파일 검사 거절’을 고르면 앱은 PNG라는 이름으로 잘못된 내용을 보냅니다. 서버는 HTTP 422로 거절하며 원본 파일·게시글·첨부 연결이 모두 0개로 남습니다. 파일이 앱에 보였다는 사실과 서버가 받기로 했는지는 다릅니다.

앱에서 연결하는 일

사진 파일을 실제 요청에 담고 전송 중·성공·거절 상태를 구분합니다.

서버에서 구현하는 일

파일의 허용 조건과 실제 내용을 확인하고, 통과한 파일만 저장 단계로 넘깁니다.

요청 값과 구현 기준 자세히 보기
const form = new FormData();
form.append("file", file);

const response = await fetch("/api/attachments", {
  method: "POST",
  body: form
});
// multipart 경계 값은 브라우저가 넣도록 합니다.
// 실패 응답이면 첨부 번호를 만들지 않습니다.

구현 과정 03

사진 원본을 저장하고, 글에 사용할 첨부 번호를 돌려줍니다

검사를 통과한 사진은 서버의 파일 저장소에 보관합니다. 시연은 컴퓨터 안의 전용 폴더에 원본 바이트를 저장하며, 고객의 파일 이름을 그대로 경로로 쓰지 않고 서버가 만든 이름을 사용합니다.

데이터베이스에는 사진 바이트를 다시 넣는 대신 첨부 번호, 저장 위치, 크기, 현재 연결 상태를 남깁니다. 이 구조에서 파일 저장소는 사진 원본을, 데이터베이스는 파일을 찾고 연결하는 정보를 맡습니다. 파일 저장 방식을 실제 서비스에 맞게 고를 때도 두 역할을 구분합니다.

서버는 저장을 마친 뒤 첨부 번호 F-001을 앱에 응답합니다. 앱은 이 번호를 보관해 다음 글 등록 요청에 사용합니다. 이 시점에는 파일 1개·게시글 0개·첨부 연결 0개가 맞는 결과입니다. 파일 전송 완료를 글 등록 완료로 표시하면 안 됩니다.

파일 저장과 첨부 정보 기록 사이에도 오류가 날 수 있습니다. 시연은 첨부 정보 기록이 실패하면 방금 쓴 파일을 정리합니다. 실제 운영에서는 중간에 서버가 중단되는 경우까지 고려해 남은 파일을 찾고 정리하는 기준을 별도로 준비합니다.

앱에서 연결하는 일

서버가 돌려준 첨부 번호를 보관하고, 사진 전송 완료와 글 등록 전을 구분합니다.

서버에서 구현하는 일

사진 원본과 조회 정보를 저장하고, 현재 범위에서 사용할 수 있는 첨부 번호를 응답합니다.

요청 값과 구현 기준 자세히 보기
HTTP 201
{
  "attachmentId": "F-001",
  "state": "UNATTACHED",
  "bytes": 2358018,
  "width": 1536,
  "height": 1024
}

원본 파일 1개 / 게시글 0개 / 첨부 연결 0개
사진 원본은 파일 저장소에 보관하고 첨부 번호 F-001을 게시글 P-001에 연결해 조회하는 과정.
사진 원본은 파일 저장소에 보관하고 첨부 번호 F-001을 게시글 P-001에 연결해 조회하는 과정. 이미지를 눌러 크게 볼 수 있습니다.

구현 과정 04

글 등록 요청은 제목과 첨부 번호를 함께 보냅니다

사용자가 글을 등록하면 앱은 제목과 첨부 번호 F-001을 게시글 API로 보냅니다. 앞에서 파일 저장을 마쳤으므로 사진을 다시 전송하지 않습니다. 서버는 요청을 보낸 범위가 그 첨부를 사용할 수 있는지, 이미 다른 글에 연결한 파일은 아닌지 확인합니다.

조건을 통과하면 게시글 P-001을 만들고 P-001과 F-001의 연결을 남깁니다. 시연에서는 게시글 행, 첨부 연결 행, 첨부 상태 변경을 SQLite 트랜잭션으로 묶습니다. 이 데이터베이스 변경들이 함께 확정돼야 글과 사진의 관계가 맞습니다.

같은 글 등록 요청이 반복될 수도 있습니다. 버튼의 중복 실행을 막는 것과 별도로 서버는 등록 요청 식별값을 확인합니다. 같은 식별값으로 이미 저장한 같은 내용이면 기존 게시글 번호를 돌려주고, 다른 내용으로 바꿔 보낸 경우는 거절하도록 구성했습니다.

정상 저장을 확정하면 서버는 HTTP 201과 게시글 번호를 응답합니다. 앱은 서버가 보낸 실제 번호를 사용합니다. 아직 별도 목록·상세 조회를 하지 않았다면, 화면에서도 ‘저장 응답 수신’과 ‘조회 확인 완료’를 구분합니다.

앱에서 연결하는 일

제목·첨부 번호·같은 등록을 알아볼 식별값을 보내고 응답의 글 번호를 보관합니다.

서버에서 구현하는 일

첨부 사용 범위와 연결 상태를 확인하고 게시글·첨부 연결을 함께 확정합니다.

요청 값과 구현 기준 자세히 보기
POST /api/posts
{
  "title": "오늘의 작업 공간",
  "attachmentId": "F-001",
  "requestKey": "이번 글 등록을 구분하는 값"
}

BEGIN IMMEDIATE;
INSERT INTO posts (...);
INSERT INTO post_attachments (...);
UPDATE attachments SET state = 'ATTACHED' ...;
COMMIT;

구현 과정 05

글 저장이 실패하면 남은 사진과 재시도를 따로 처리합니다

파일 전송에는 성공했지만 글 등록이 실패할 수 있습니다. 시연의 ‘글 저장 실패·재시도’는 데이터베이스에 글과 연결 변경을 시도한 뒤, 확정 전에 한 번 실패시키는 상황입니다. 서버는 HTTP 503을 응답합니다.

이때 게시글과 첨부 연결의 변경은 취소돼 0개로 남고, 첨부 상태도 글 미연결로 돌아옵니다. 하지만 앞서 다른 요청에서 저장한 사진 원본은 남습니다. 데이터베이스의 트랜잭션이 파일 저장소의 파일까지 자동으로 되돌리지는 않습니다.

앱은 글 제목과 유효한 첨부 번호 F-001을 유지하고, 같은 글 등록을 다시 시도하게 합니다. 이 시연에서는 재시도 후 원본 파일을 한 개 더 만들지 않고 파일 1개·게시글 1개·첨부 연결 1개가 됩니다.

사용자가 글쓰기를 끝내지 않으면 미연결 파일이 남을 수 있습니다. 실제 서비스에서는 보관 기간, 아직 전송·편집 중인 파일인지, 다른 글이 참조하는지 등을 확인해 정리 기준을 정합니다. 이 시연은 미연결 상태를 보여주며, 자동 보관 기한이나 운영 정리 작업을 실행하지는 않습니다.

앱에서 연결하는 일

제목과 첨부 번호를 잃지 않고, 실패한 글 등록부터 다시 시도합니다.

서버에서 구현하는 일

글·연결 변경을 취소하고 남은 파일의 미연결 상태를 관리합니다.

요청 값과 구현 기준 자세히 보기
파일 전송: HTTP 201 → F-001 저장
글 등록 실패: HTTP 503 → DB 변경 ROLLBACK

원본 파일: 1개 유지
게시글: 0개
첨부 연결: 0개

같은 F-001로 재시도 → P-001 저장
최종: 원본 1개 / 게시글 1개 / 연결 1개
글 저장 실패 때 파일 1개·게시글 0개·연결 0개가 남고, 같은 첨부로 재시도하면 각 1개가 되는 과정.
글 저장 실패 때 파일 1개·게시글 0개·연결 0개가 남고, 같은 첨부로 재시도하면 각 1개가 되는 과정. 이미지를 눌러 크게 볼 수 있습니다.

구현 과정 06

저장 응답 뒤에는 목록·상세·사진을 다시 읽어 확인합니다

마지막 검수에서는 등록 응답만 보지 않습니다. 목록 API를 다시 호출해 P-001이 나타나는지 확인하고, 상세 API를 따로 호출해 제목과 첨부 F-001이 연결돼 있는지 읽습니다. 상세에서 받은 파일 주소로 사진도 다시 표시합니다.

이렇게 확인하면 앱이 처음에 만든 미리보기를 계속 보여주면서 저장된 것처럼 보이는 문제를 구분할 수 있습니다. 시연의 ‘목록·상세 다시 읽기’를 누르면 실제 GET 요청과 응답을 연결 기록에서 볼 수 있습니다.

검수는 정상 저장뿐 아니라 파일 내용 거절, 파일 업로드 후 글 저장 실패, 같은 첨부로 재시도, 같은 등록 요청 반복을 포함합니다. 화면의 안내와 서버의 저장 결과가 같은 상태를 가리켜야 합니다.

여러 장 첨부, 사진 교체·삭제, 영상 변환, 공개·비공개 파일 열람처럼 서비스에 필요한 기능은 별도로 범위를 정해 이어갑니다. 사진 한 장의 구현에서도 앱 화면, 파일 저장소, 데이터베이스와 실패 뒤의 행동을 함께 맞추는 것이 기본입니다.

앱에서 연결하는 일

서버의 목록과 상세를 따로 읽고, 상세에서 받은 사진을 표시합니다.

서버에서 구현하는 일

저장된 글과 첨부 관계를 다시 조회해 같은 결과를 제공합니다.

요청 값과 구현 기준 자세히 보기
GET /api/posts
→ items 안에 P-001이 있는지 확인

GET /api/posts/P-001
→ title, attachments[0].attachmentId = F-001 확인

GET /api/files/F-001
→ 저장된 사진 바이트 다시 읽기

응답 기록과 파일 해시·연결 행을 함께 대조합니다.
처리 원리의 참고 자료

MDN · FormData로 파일 보내기 · 파일을 요청에 담는 방식과 multipart 경계 값의 처리를 확인합니다.

SQLite · 트랜잭션 · 데이터베이스 변경을 함께 확정하거나 되돌리는 기준을 확인합니다.

앱의 사진 첨부와 저장 흐름을 함께 정리하세요

어떤 사용자가 어떤 사진을 올리고, 어디에 연결해 이용하는지 알려 주세요. 파일 저장부터 실패 뒤의 행동까지 필요한 개발 범위를 함께 나눕니다.

30분 무료 상담 신청
앱이 만들어지는 방식으로 돌아가기