Skip to content

Instantly share code, notes, and snippets.

@saleebm
Created February 17, 2021 19:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saleebm/c6e05ab94ecb43addb11398b31940d49 to your computer and use it in GitHub Desktop.
Save saleebm/c6e05ab94ecb43addb11398b31940d49 to your computer and use it in GitHub Desktop.
handle image upload using next.js API routes
import { NextApiRequest } from 'next'
import formidable from 'formidable'
import { promises as fs } from 'fs'
import path from 'path'
import sanitize from 'sanitize-filename'
import axios from 'axios'
import { handler } from '@Lib/api'
import {
UnauthenticatedError,
UnsupportedMethodError,
} from '@Lib/api/handler/known-errors'
import {
UploadImage,
UploadImageRes,
} from '@Types/api/media/upload-image'
import { requireMethod } from '@Lib/api/requests/require-method'
import { requireAuth } from '@Lib/api/auth/require-auth'
import { authService } from '@Services'
import { ACCESS_TOKEN_NAME } from '@Config/auth/tokens'
// https://nextjs.org/docs/api-routes/api-middlewares#custom-config
// turn off bodyParser to read as stream
export const config = {
api: {
bodyParser: false,
},
}
const formidablePromise = (req: NextApiRequest) => {
const uploadDir = path.resolve('public', 'static', 'uploads')
return new Promise<{
fields: formidable.Fields
files: formidable.Files
}>((resolve, reject) => {
const form = new formidable.IncomingForm()
form.uploadDir = uploadDir
form.keepExtensions = true
form.parse(req, async (err, fields, files) => {
if (err) return reject(err)
resolve({ fields, files })
})
})
}
/**
*todo check for
* Content-Type: multipart/form-data;
*/
export default handler<UploadImage, UploadImageRes>(
async (req: NextApiRequest) => {
if (!requireMethod('post')(req))
throw new UnsupportedMethodError()
const { userId } = (await requireAuth(req)) || {}
if (!userId) throw new UnauthenticatedError()
const authTokenInput = authService.getToken(ACCESS_TOKEN_NAME, {
req,
})
const types = [
'image/png',
'image/jpeg',
'image/gif',
'image/svg+xml',
]
const { files } = await formidablePromise(req)
if (files && 'image' in files) {
if (!types.includes(files.image.type))
throw new Error('Bad mime type for image')
const purifyName = sanitize(files.image.name, {
replacement: '_',
})
const filename = `${new Date().getTime()}${purifyName}`.replace(
' ',
'',
)
const desiredFilename = path.resolve(
'public',
'static',
'uploads',
filename,
)
await fs.rename(files.image.path, desiredFilename)
const { data } = await axios({
url: `${process.env.NEXT_PUBLIC_API_REST_ENDPOINT}/wp/v2/media/`,
method: 'POST',
headers: {
'Content-Disposition': `attachment; filename="${files.image.name}"`,
'Authorization': `Bearer ${authTokenInput}`,
'Content-Type': files.image.type,
},
data: await fs.readFile(desiredFilename),
maxContentLength: Infinity,
})
return {
sourceUrl: data.source_url,
}
} else {
throw new Error('failed to upload image')
}
},
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment