Next.js file upload with uppy and multer using api routes serverless backend

December 01, 2020

Using Uppy for the upload client

Uppy is a wonderful library to setup your file upload client in react (or Next.js, that’s what I am using). It does require a little setup, but it’s really versatile and straightforward to use.

I am not doing anything crazy, just wanted to enable file upload in my Next.js project, here is the UI component with some basic setup.

import React from "react"
import Uppy from "@uppy/core"
import { DragDrop } from "@uppy/react"
import ThumbnailGenerator from "@uppy/thumbnail-generator"
import XHRUpload from "@uppy/xhr-upload"

const uppy = new Uppy({
  meta: { type: "iphoneAdPix" },
  restrictions: {
    maxNumberOfFiles: 3,
    maxFileSize: 1048576 * 4,
    allowedFileTypes: [".jpg", ".jpeg", ".png"],
  },
  autoProceed: true,
})

uppy.use(XHRUpload, {
  endpoint: "/api/adPix",
  fieldName: "iphoneAdPix",
  formData: true,
})

uppy.use(ThumbnailGenerator, {
  thumbnailWidth: 200,
  waitForThumbnailsBeforeUpload: false,
})

uppy.on("thumbnail:generated", (file, preview) => {
  console.log(file.name, preview)
})

uppy.on("complete", result => {
  const url = result.successful[0].uploadURL
  console.log("successful upload", result)
})

uppy.on("error", error => {
  console.error(error.stack)
})

uppy.on("restriction-failed", (file, error) => {
  const err = error.stack.includes("exceeds maximum allowed size of 4 MB")
    ? "A fájl mérete nagyobb mint 4MB"
    : error

  alert(
    "Feltöltési hiba: " +
      err +
      "\n" +
      file.name +
      " Mérete : " +
      Math.round(file.size / 1024 / 1024) +
      "MB"
  )
})

/*

    From:   https://uppy.io/examples/dashboard/
            https://uppy.io/docs/react/

 */

const ImageUpload = () => {
  return (
    <div>
      <DragDrop
        uppy={uppy}
        locale={{
          strings: {
            // Text to show on the droppable area.
            // `%{browse}` is replaced with a link that opens the system file selection dialog.
            dropHereOr: "Húzd ide a képet vagy %{browse}",
            // Used as the label for the link that opens the system file selection dialog.
            browse: "keress az eszközön",
          },
        }}
      />
    </div>
  )
}

export default ImageUpload

Make sure to install the necessary packages with npm/yarn:

  • @uppy/core
  • @uppy/react
  • @uppy/xhr-upload
  • @uppy/thumbnail-generator (nice to have)

The above really provides a simple user interface, but that’s more than enough to get started. Please note the use of the XHRUpload plugin, this is the interesting part.

We need a file upload backend. Use multer and Next.js api routes

As I am using Next.js, I have the luxury of being able to write serverless backend code with the help of api routes.

For the image upload backend I am using multer, which was a bit tricky to find out how to implement in Next.js. There are tons of instructions for plain javascript, express, etc… implementations, but not a lot for Next.js. It should be a breeze, as it’s just React and javascript, however it took me a while to pull it together.

The api routes are stored under /pages/api so this file would be /pages/api/adPix.js.

There we go:

import multer from "multer"

export const config = {
  api: {
    bodyParser: false,
  },
}

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "./public/uploads")
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname)
  },
})

var upload = multer({ storage: storage })

export default (req, res) => {
  upload.array("iphoneAdPix", 3)(req, {}, err => {
    // do error handling here
    console.log(req.files) // do something with the files here
  })
  res.status(200).send({})
}

Make sure to npm i -S multer. What the above does is, it receives the files from the client (uppy implementation) and saves them under the ./public/uploads directory.

Do not forget, Next.js api calls only run on the server side.

There is much more that you can configure multer to do, consult the docs for that.

Hope this helps. emoji-thumbsup