# Backend File Upload

> NestJS Fastify multipart file uploads with two-phase storage: FileFieldsInterceptor, StorageService addPathToFiles, saveFilesOnServer, rollback on failure. Use when implementing image uploads, multipart endpoints, or file storage in backend modules.

- Skill: `xmuhameed/backend-file-upload` (Agent Skill)
- Install (CLI): `npx skillmds@latest add xmuhameed/backend-file-upload`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xmuhameed/backend-file-upload/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: xmuhameed (https://skillmd.com/u/xmuhameed)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/xmuhameed/backend-file-upload

---


# Backend File Upload

**No file entity table** — URLs stored as string columns (`imageUrl`, `coverUrl`).

## Controller

```typescript
@ApiConsumes('multipart/form-data')
@UseInterceptors(FileFieldsInterceptor([
  { name: 'imageUrl', maxCount: 1 },
  { name: 'coverUrl', maxCount: 1 },
]))
@Post('create-category')
async create(
  @UploadedFiles() files: { imageUrl?: MemoryStorageFile[]; coverUrl?: MemoryStorageFile[] },
  @Body() dto: CreateCategoryDto,
) {
  const imageFile = files?.imageUrl?.[0];
  const coverFile = files?.coverUrl?.[0];
  return sendSuccessfulResponse(await this.service.create(dto, imageFile, coverFile));
}
```

DTO fields use `@RequiredFileField('image')` / `@OptionalFileField('cover')`.

## Two-Phase Save (create)

```
1. prisma.create({ ...data, imageUrl: null })     — record without URL
2. folder = `{plural}/{singular}-{id}`            — e.g. categories/category-{uuid}
3. files = storage.addPathToFiles([file], folder) — UUID path + relative URL
4. if no files → delete record → throw BAD_REQUEST
5. prisma.update({ imageUrl: files[0].fileurl })
6. storage.saveFilesOnServer(files)               — write disk
7. on save failure → throw (record may need cleanup)
```

## Update (optional new file)

```typescript
if (file) {
  const files = await this.storage.addPathToFiles([file], `${folder}`);
  data.imageUrl = files[0].fileurl;
  await this.prisma.update({ where: { id }, data });
  await this.storage.saveFilesOnServer(files);
  if (existing.imageUrl) await this.storage.deleteFile(existing.imageUrl);
}
```

## StorageService Methods

| Method | Purpose |
|--------|---------|
| `addPathToFiles(files, folder)` | Assign UUID filename + URL |
| `saveFilesOnServer(files)` | Write buffers to disk |
| `deleteFile(url)` | Remove file |
| `getClientFiles(files)` | Normalize multipart input |

## URL Format

`/{folder}/{uuid}-{timestamp}-{fieldname}.{ext}` served from `uploads/` via `@fastify/static`.

## Multipart Config (main.ts)

Max 500MB, 10 files. `LIMIT_FILE_SIZE` → HTTP 412.

## Module

Import `StorageModule` in feature module. `@Global()` — inject `StorageService` anywhere.

