画像アップロード「Uploadthing」を使って、Nuxt3×Djnagoアプリから画像をアップロードする

# SaasサービスのUploathingとは？
`Uploadthing`の詳細は、下記の記事に記載していますので、今回は割愛します。

https://qiita.com/SKY-HaYaTo/items/8b3026fc9efe71eadbdf

# 完成イメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/706d868b-8447-4e77-95b0-02c145f39b81.png)


# ディレクトリ構成
`frontend`フォルダにNuxt3プロジェクトを、`backend`フォルダにDjangoプロジェクトをそれぞれ格納します。

```
.
└── NuxtDjangoProject/
    ├── backend/
    │   ├── adminuser
    │   ├── api/
    │   │   ├── migrations/
    │   │   │   └── 0001_initial.py
    │   │   ├── serializer.py
    │   │   ├── urls.py
    │   │   └── views.py
    │   └── todoproject/
    │       ├── settings.py
    │       └── urls.py
    └── frontend/
        ├── components/
        │   ├── footer/
        │   │   └── footer.vue
        │   └── navbar/
        │       └── navbar.vue
        ├── pages/
        │   ├── adminUsers/
        │   │   ├── login/
        │   │   │   └── login.vue
        │   │   ├── registration/
        │   │   │   └── registration.vue
        │   │   ├── updateProfile/
        │   │   │   └── updateProfile.vue
        │   │   └── uploadImageFile/
        │   │       └── uploadImageFile.vue
        │   ├── login/
        │   │   └── login.vue
        │   ├── registration/
        │   │   └── registration.vue
        │   └── index.vue
        ├── .env
        ├── server/
        │   └── uploadthing.ts
        ├── app.vue
        ├── nuxtconfig.ts/
        │   └── package.json
        └── tsxconfig.json
```

# Nuxt3とUploadthingの連携方法

`uploadthing`の下記の公式ドキュメントを参照しながら進めていきます。

https://docs.uploadthing.com/getting-started/nuxt

### パッケージをインストールする

`uploadthing`をNuxt3でも使えるようにパッケージをインストールしましょう。

`frontend`フォルダ直下に移動して、下記のコマンドをコマンドパレットに入力します。

```
npm install uploadthing @uploadthing/nuxt
```

### `.env`ファイルにトークンを追加する

`.env`ファイルに`Uploadthing`にトークンを追加します。

```
UPLOADTHING_TOKEN='your access token'
```

### Uploadthingモジュールを初期化する

`@uploadthing/nuxt`モジュールを`nuxtconfig.ts`に追加します。


```nuxtconfig.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2024-11-01',
  devtools: { enabled: true },
  modules: ['vuetify-nuxt-module', '@pinia/nuxt','@uploadthing/nuxt'],
  pinia:{
    authImports:[
      'defineStore'
    ],
  },
})
```

### ファイルアップロード機能をサーバ側(Typescript)に追加する
画像をサーバ経由で`uploadthing`へアップ路度するための機能を実装します。

```frontend/server/uploadthing.ts

import type { H3Event } from "h3";

import { createUploadthing } from "uploadthing/h3";
import type { FileRouter } from "uploadthing/h3";

const f = createUploadthing();

const auth = (ev: H3Event) => ({ id: "fakeId" }); // Fake auth function

// FileRouter for your app, can contain multiple FileRoutes
export const uploadRouter = {
  // Define as many FileRoutes as you like, each with a unique routeSlug
  imageUploader: f({
    image: {
      maxFileSize: "4MB",
      maxFileCount: 1,
    },
  })
    // Set permissions and file types for this FileRoute
    .middleware(async ({ event }) => {
      // This code runs on your server before upload
      const user = await auth(event);

      // If you throw, the user will not be able to upload
      if (!user) throw new Error("Unauthorized");

      // Whatever is returned here is accessible in onUploadComplete as `metadata`
      return { userId: user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      // This code RUNS ON YOUR SERVER after upload
      console.log("Upload complete for userId:", metadata.userId);

      console.log("file url", file.ufsUrl);
    }),
} satisfies FileRouter;

export type UploadRouter = typeof uploadRouter;

```

### 画面を作成する

サーバまでの機能を実装をしたので、最後に画面を作っていきます。

```frontend/pages/adminUsers/uploadImageFile/uploadImageFile.vue
<script setup lang="ts">
import { useRouter } from 'vue-router';
const router = useRouter();
const alert = (msg: string) => {
  window.alert(msg);
  
};
</script>

<template>
  <div>
    <UploadButton
      :config="{
        endpoint: 'imageUploader',
        onClientUploadComplete: (file) => {
          console.log('uploaded', file);
          console.log('uploaded', file[0].url);
          alert('Upload complete');
          router.push(`/adminUsers/updateProfile/updateProfile?image=${file[0].url}`);
          
        },
        onUploadError: (error) => {
          console.error(error, error.cause);
          alert('Upload failed');
        },
      }"
    />
    <UploadDropzone
      :config="{
        endpoint: 'imageUploader',
        onClientUploadComplete: (file) => {
          console.log('uploaded', file);
          //console.log('uploaded', file[0].url);
          alert('Upload complete');
        },
        onUploadError: (error) => {
          console.error(error, error.cause);
          alert('Upload failed');
        },
      }"
    />
  </div>
</template>

```

以上です。