Hi this can be achieved without any libraries, Next.js 13 already supports FormData
!
This is the code I used in my project, it will definitely help you.
For context: the feature I implemented is allowing users to update their profile image.
// In your React client component
const [file, setFile] = useState<File | null>(null)
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!file) throw new Error('No file selected')
const { imageUrl } = await uploadProfileImage(file)
console.log('New image URL', imageUrl)
}
const uploadProfileImage = async (file: File) => {
const body = new FormData()
body.set('image', file)
const response = await fetch('/api/upload/profile-image', {
method: 'POST',
body,
})
if (!response.ok) {
throw new Error('Error uploading profile image')
}
const result: UploadProfileImageResponse = await response.json()
if (!result) throw new Error('Error uploading profile image')
return result
}
// In `src/app/api/upload/profile-image/route.ts`
import sharp from 'sharp'
import { uploadToS3 } from '~/server/aws'
import { db } from '~/server/db/db'
import { eq } from 'drizzle-orm'
import { users } from '~/server/db/schema'
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '~/server/auth'
export async function POST(request: NextRequest) {
// Step 1: Check if user is authenticated (With NextAuth)
const session = await getServerSession(authOptions)
if (!session) {
return NextResponse.json(null, { status: 401 })
}
// Step 2: Get image from request (With Next.js API Routes)
const formData = await request.formData()
const imageFile = formData.get('image') as unknown as File | null
if (!imageFile) {
return NextResponse.json(null, { status: 400 })
}
const imageBuffer = Buffer.from(await imageFile.arrayBuffer())
// Step 3: Resize image (With Sharp)
const editedImageBuffer = await sharp(imageBuffer)
.resize({ height: 256, width: 256, fit: 'cover' })
.toBuffer()
// Step 4: Upload image (With AWS SDK)
const imageUrl = await uploadToS3({
buffer: editedImageBuffer,
key: `profile-images/${session.user.id}`,
contentType: imageFile.type,
})
// Step 5: Update user in database (With Drizzle ORM)
await db
.update(users)
.set({
image: imageUrl,
})
.where(eq(users.id, session.user.id))
// Step 6: Return new image URL
return NextResponse.json({ imageUrl })
}
// Export types for API Routes
export type UploadProfileImageResponse = ExtractGenericFromNextResponse<
Awaited<ReturnType<typeof POST>>
>
type ExtractGenericFromNextResponse<Type> = Type extends NextResponse<infer X>
? X
: never
// In `src/server/aws.ts`
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
export const BUCKET_REGION = 'my-bucket-region'
export const BUCKET_NAME = 'my-bucket-name'
export const s3 = new S3Client({
region: BUCKET_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'missing-env',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'missing-env',
},
})
export async function uploadToS3<K extends string>({
buffer,
key,
contentType,
}: {
buffer: Buffer
key: K
contentType: string
}) {
await s3.send(
new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: key,
Body: buffer,
ContentType: contentType,
})
)
return `https://${BUCKET_NAME}.s3.${BUCKET_REGION}.amazonaws.com/${key}` as const
}