반응형
https://www.youtube.com/watch?v=Av9C7xlV0fA
4:37:27
핵심 개념
- Storage: 실제 파일(이미지) 저장소
- Database: 파일의 "위치"(ID)를 기록
- FormData: File 객체를 서버로 전송하는 유일한 방법
이미지를 보여줄 때는 imageUrl(파일 ID)로 Storage에서 실제 이미지를 가져오면 됨!
💡 전체 흐름 요약
| 1. 사용자가 이미지 선택 ↓ 2. FormData로 서버에 전송 ↓ 3. Zod 스키마 검증 (File 또는 string) ↓ 4. Storage에 이미지 업로드 ↓ 5. 생성된 파일 ID 반환 ↓ 6. Database에 workspace 생성 (imageUrl 포함) |
* 스키마 정의 (src/features/workspaces/schemas.ts)
- File 객체 (새로운 이미지 업로드) 또는
- string (기존 이미지 URL)을 받을 수 있도록 설정
- 빈 문자열은 undefined로 변환
import {z} from "zod"
export const createWorkspaceSchema = z.object({
name: z.string().trim().min(1, "Required"),
image : z.union([
z.instanceof(File),
z.string().transform((value) => value === "" ? undefined : value),
]).optional(),
})
src/features/workspaces/server/route.ts
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { createWorkspaceSchema } from "../schemas";
import { sessionMiddleware } from "@/lib/session-middleware";
import { DATABASE_ID, WORKSPACES_ID } from "@/config";
import { ID } from "node-appwrite";
const app = new Hono()
.post(
"/",
zValidator("json", createWorkspaceSchema),
sessionMiddleware,
async (c) => {
const databases = c.get("databases")
const user = c.get("user")
const {name, image} = c.req.valid("json")
const workspace = await databases.createDocument(
DATABASE_ID,
WORKSPACES_ID,
ID.unique(),
{
name,
userId:user.$id
}
);
return c.json({data:workspace})
}
)
export default app
* Appwrite 설정
Storage Bucket 생성 (이미지 파일들을 저장할 공간)
- Appwrite Console → Storage → images bucket 생성



Database Column 추가
- Workspaces collection에 imageUrl 컬럼 추가
- 업로드된 이미지의 ID를 저장할 필드

환경변수 설정
.env.local
...
NEXT_PUBLIC_APPWRITE_IMAGES_ID=여기넣어주기
src/config.ts
export const DATABASE_ID = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID!;
export const WORKSPACES_ID = process.env.NEXT_PUBLIC_APPWRITE_WORKSPACES_ID!;
export const IMAGES_BUCKET_ID = process.env.NEXT_PUBLIC_APPWRITE_IMAGES_ID!;
* 서버 라우트 수정 (src/features/workspaces/server/route.ts)
① JSON → Form 데이터로 변경
- File 객체는 JSON으로 전송할 수 없고, FormData로만 전송 가능
② Storage 가져오기
- Appwrite Storage API를 사용하여 파일 업로드
동작 순서:
- image가 File 객체인지 확인
- Storage에 파일 업로드 → 고유 ID 생성됨
- 생성된 파일 ID(file.$id)를 uploadedImageUrl에 저장
- Database에 workspace 생성 시 imageUrl 필드에 저장
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { createWorkspaceSchema } from "../schemas";
import { sessionMiddleware } from "@/lib/session-middleware";
import { DATABASE_ID, IMAGES_BUCKET_ID, WORKSPACES_ID } from "@/config";
import { ID } from "node-appwrite";
const app = new Hono()
.post(
"/",
zValidator("form", createWorkspaceSchema), ①
sessionMiddleware,
async (c) => {
const databases = c.get("databases")
const storage = c.get("storage") ②
const user = c.get("user")
const {name, image} = c.req.valid("form") ①
let uploadedImageUrl: string | undefined;
console.log('data', image);
if(image instanceof File){
// 안토니오 방법
// const file = await storage.createFile(
// IMAGES_BUCKET_ID,
// ID.unique(),
// image
// );
// const arrayBuffer = await storage.getFilePreview(
// IMAGES_BUCKET_ID,
// file.$id
// )
// uploadedImageUrl = `data:image/png;base64,${Buffer.from(arrayBuffer).toString('base64')}`
// 수정된 코드
if(image instanceof File){
// 1. Storage에 파일 업로드
const file = await storage.createFile(
IMAGES_BUCKET_ID, // 어디에 저장할지
ID.unique(), // 고유한 파일 ID 생성
image // 업로드할 파일
);
// 2. 파일 ID를 URL로 저장
uploadedImageUrl = file.$id
}
}
const workspace = await databases.createDocument(
DATABASE_ID,
WORKSPACES_ID,
ID.unique(),
{
name,
userId:user.$id,
imageUrl: uploadedImageUrl
}
);
return c.json({data:workspace})
}
)
export default app


⚠️ 주의사항
안토니오 방법 vs 수정된 코드
// 안토니오: Base64로 인코딩하여 저장 (무거움)
uploadedImageUrl = `data:image/png;base64,${...}`
// 수정: 파일 ID만 저장 (가벼움, 권장)
uploadedImageUrl = file.$id
왜 수정했는가?
- Base64는 이미지를 문자열로 변환 → 용량 33% 증가
- 파일 ID만 저장하면 나중에 storage.getFilePreview()로 가져올 수 있음
반응형
'Clone Coding' 카테고리의 다른 글
| Jira-clone - 워크스페이스 멤버십 시스템 만들기 (0) | 2025.10.02 |
|---|---|
| Jira-clone - Workspace 목록 불러오기 (0) | 2025.10.01 |
| Jira-clone - Toaster 알림 라이브러리사용 (0) | 2025.09.27 |
| Jira-clone - Appwrite로 워크스페이스 생성 기능 구현 (0) | 2025.09.26 |
| Jira-Clone - 사이드바 (0) | 2025.09.25 |