ネットを探しても、なかなか見つからないDrop Zoneからの画像アップロード方法。
最近Shopifiyアプリが、Next.jsで作成できるようになった。
jsだけで、フロントもバックエンドも作成できる。
Shopify App CLI (Github)
https://github.com/Shopify/shopify-app-cli
Shopify App CLIでコマンドから簡単にデプロイできるのは、herokuだけ。
heroku
https://jp.heroku.com/
Next.jsで、Shopify Polaris Drop Zone コンポーネントを使用して、AWS S3への画像のアップロードのサンプルを作成した。
このサンプルは、S3へブラウザからダイレクトにアップロードされる。
サーバー側は、 S3へのアップロード用の署名付きURLを発行して、返すだけ。
画像は、サーバーを通さないので、負荷がない。
ライブラリ
Shopify App CLIで作成した、node.jsのアプリケーションに、画像アップロード機能を追加。
Shopify App CLI (Github)
https://github.com/Shopify/shopify-app-cli
Shopify Polaris Drop Zone
https://polaris.shopify.com/components/actions/drop-zone
サンプル(Github)
設定
ダウンロード
yarn
環境設定
.env.exampleをコピーして.envファイルを作成
項目を埋める
SHOPIFY_API_KEY=YOUR_SHOPIFY_API_KEY
SHOPIFY_API_SECRET=YOUR_SHOPIFY_SECRET
HOST=YOUR_TUNNEL_URL
SHOP=my-shop-name.myshopify.com
SCOPES=read_products
AWS_ACCESS_KEY_ID=xxxxx
AWS_SECRET_ACCESS_KEY=yyyy
BUCKET=S3 BUCKET Name
S3のCORSの設定
ShopifyアプリからS3にアップロードできるように。
(本番環境では、AllowedOriginを設定する)
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
開発環境 スタート
shopify serve
画像のアップロード
画像を選択して、Drop Zoneへドラッグアンドドロップする。
アップロードされたファイルが、Drop Zoneへサムネイル画像付きで表示される
S3にファイルがアップロードされる。
バックエンド
server.js
S3へのアップロード用の署名付きURLを返す
router.get("/api/upload-image", verifyRequest(), async (ctx) => {
const url = await uploadImage(ctx.query)
ctx.res.statusCode = 200
ctx.res.setHeader("Content-Type", "application/json")
ctx.res.end(JSON.stringify({ url: url }))
})
upload-image.js
S3へのアップロード用の署名付きURLを作成
const aws = require("aws-sdk")
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY
const BUCKET = process.env.BUCKET
aws.config.update({
region: "ap-northeast-1",
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
})
export default async (file) => {
const s3 = new aws.S3()
const params = {
Bucket: BUCKET,
Key: file.fileName,
Expires: 60,
ContentType: file.fileType,
}
return new Promise((resolve, reject) => {
s3.getSignedUrl("putObject", params, (err, url) => {
if (err) {
reject(err)
}
resolve(url)
})
})
}
フロントエンド
index.js
Drop ZoneをラップしたImageFileUploderを表示
import { Heading, Page } from "@shopify/polaris"
import React from "react"
import ImageFileUploder from "../components/ImageFileUploder"
const Index = () => {
return (
<Page>
<Heading>Drop Zone Image Upload S3</Heading>
<ImageFileUploder />
</Page>
)
}
export default Index
ImageFileUploder.js
Drop ZoneをラップしたImageFileUploder
import { Caption, DropZone, Stack, Thumbnail } from "@shopify/polaris"
import React, { useCallback, useState } from "react"
import axios from "axios"
const BUCKET = process.env.BUCKET
const ImageFileUploader = () => {
const [files, setFiles] = useState([])
const uploadImage = async (file) => {
return axios
.get("/api/upload-image", {
params: {
fileName: file.name,
fileType: file.type,
},
})
.then((res) => {
const options = {
headers: {
"Content-Type": file.type,
},
}
return axios.put(res.data.url, file, options)
})
.then((res) => {
const { name } = res.config.data
return {
name,
isUploading: true,
url: `https://${BUCKET}.s3.amazonaws.com/${file.name}`,
}
})
}
const handleDropZoneDrop = useCallback(
async (_dropFiles, acceptedFiles, _rejectedFiles) => {
for (let i = 0; i < acceptedFiles.length; i++) {
await uploadImage(acceptedFiles[i])
}
setFiles((files) => [...files, ...acceptedFiles])
},
[]
)
const validImageTypes = ["image/gif", "image/jpeg", "image/png"]
const fileUpload = !files.length && <DropZone.FileUpload />
const uploadedFiles = files.length > 0 && (
<Stack vertical>
{files.map((file, index) => (
<Stack alignment="center" key={index}>
<Thumbnail
size="small"
alt={file.name}
source={
validImageTypes.indexOf(file.type) > 0
? window.URL.createObjectURL(file)
: "https://cdn.shopify.com/s/files/1/0757/9955/files/New_Post.png?12678548500147524304"
}
/>
<div>
{file.name} <Caption>{file.size} bytes</Caption>
</div>
</Stack>
))}
</Stack>
)
return (
<DropZone type="image" onDrop={handleDropZoneDrop}>
{uploadedFiles}
{fileUpload}
</DropZone>
)
}
export default ImageFileUploader
ImageFileUploder.js
画像のアップロード部分
サーバーへ、/api/upload-image
ファイル名とファイルタイプを送信。
サーバーから、S3アップロード用の署名付きURLが返される
署名付きURLへ、画像をPUT。
const uploadImage = async (file) => {
return axios
.get("/api/upload-image", {
params: {
fileName: file.name,
fileType: file.type,
},
})
.then((res) => {
const options = {
headers: {
"Content-Type": file.type,
},
}
return axios.put(res.data.url, file, options)
})
.then((res) => {
const { name } = res.config.data
return {
name,
isUploading: true,
url: `https://${BUCKET}.s3.amazonaws.com/${file.name}`,
}
})
}
サンプル(Github)
S3へブラウザからのダイレクトアップロードは、便利だ。