Test Category

Test Blog Post

Starter template for writing out a blog post using MDX/JSX and Next.js.

No Name Exists

Abdullah Muhammad

Published on May 17, 20265 min read 4 views

Share:
Article Cover Image

Introduction

In a past article, we dove deep into the AWS S3 service and saw how we can utilize the AWS SDK to programmatically handle object storage.

In this article, we will look at another service known as UploadThing. You can think of it as a better version of AWS S3.

The article will be short and assumes the reader is familiar with the AWS S3 storage service. Feel free to explore this article where I covered AWS S3 in detail.

UploadThing is a rich library focused on helping developers integrate object storage functionality into their apps using any web framework of their liking.

This includes frameworks such as Next.js (App/Pages router), Remix, Solid, Vue, and many more. UploadThing also provides support for back-end frameworks such as Express and Fastify.

Additional information on what frameworks are supported can be found in the official docs.

Project Setup

To get started, you will need to sign up an account with UploadThing and create a new project which will contain an API key in the project dashboard.

For the purposes of this article, we will stick with the Next.js (App router) framework as this is something we have worked with in the past.

We will touch on configuring the front-end and back-end to handle the basic operations of the UploadThing library.

There are two ways one can upload files using this library:

  • Client-side
  • Server-side

The documentation extensively covers how users can upload files either side, but for this article, we will focus on uploading files client-side.


Configuration

We will setup a front-end which allows users to upload files to the cloud and setup a back-end which will be dedicated to handling the file upload process.

The following code details a front-end React component for handling file uploads:

GitHub GistTSX
"use client";

import { useState } from "react";
import { useUploadThing } from "@/lib/uploadthing";

export default function FileDropzone() {
  const [isUploading, setIsUploading] = useState(false);
  const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);

  const { startUpload } = useUploadThing("imageUploader", {
    onClientUploadComplete: (res) => {
      const url = res?.[0]?.url;
      if (url) {
        setUploadedUrl(url);
      }
      setIsUploading(false);
    },
    onUploadError: (error) => {
      alert(`Upload failed: ${error.message}`);
      setIsUploading(false);
    }
  });

  const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files || []);
    if (files.length > 0) {
      setIsUploading(true);
      startUpload(files);
    }
  };

  return (
    <div className="space-y-4">
      <input
        type="file"
        onChange={handleFileInput}
        disabled={isUploading}
        accept="image/*"
        className="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 focus:outline-none"
      />

      {isUploading && <p className="text-sm text-gray-600">Uploading...</p>}

      {uploadedUrl && (
        <div className="p-4 bg-green-50 border border-green-200 rounded-lg">
          <p className="text-sm text-green-800 font-medium mb-2">Upload successful!</p>
          <a
            href={uploadedUrl}
            target="_blank"
            rel="noopener noreferrer"
            className="text-sm text-blue-600 hover:underline break-all"
          >
            {uploadedUrl}
          </a>
        </div>
      )}
    </div>
  );
}
Front-end client component handling file uploads using the UploadThing library

We use a custom hook (useUploadThing) which originates from the @uploadthing/react package and define a custom function that handles the file uploading process.

We define an input field that allows users to drop in their files. We also have React state which allows us to conditionally render portions of the file drop zone custom component.

A custom function is defined for handling the file input and ensuring the startUpload function is called with the requested file passed in as a parameter.

Once the file uploads correctly, a message is displayed (highlighted in green) to the user along with the file URL which points to where the user can visit the image in the browser.

UploadThing follows a similar pattern to AWS S3 in that, when an object is uploaded to storage, a URL and key is generated specifically for that object.

The key acts as a unique identifier for the uploaded object and the URL allows the user to retrieve the object in the browser.

We wrap this file drop zone custom component in a page component:

GitHub GistTSX
"use client";

import FileDropzone from "@/components/FileDropzone";

export default function Home() {
  return (
    <main className="min-h-screen p-8">
      <div className="max-w-md mx-auto">
        <h1 className="text-2xl font-bold mb-6">UploadThing Demo</h1>
        <FileDropzone />
      </div>
    </main>
  );
}
Home page component elegantly wraps the file drop zone React component

We add custom styling to ensure it looks nice and elegant to the user. Very simple and straight forward to setup.

That is all for the basic front-end UploadThing setup.


Back-end Configuration

According to the documentation, we must have a back-end route setup using this specific path: /api/uploadThing/core.ts.

In the core.ts file, we must define a file router using a function called createUploadThing which is imported from the uploadthing/next package.

The implementation looks like the following:

GitHub GistTypeScript
import { createUploadthing, type FileRouter } from "uploadthing/next";

const f = createUploadthing();

// File router definition for uploading images
export const fileRouter = {
  imageUploader: f({ image: { maxFileSize: "4MB" } }).onUploadComplete(
    async ({ file }) => {
      return { url: file.url };
    }
  ),
} satisfies FileRouter;

export type FileRouterType = typeof fileRouter;
Exporting a file router for uploading images via the back-end

We export a fileRouter object of type FileRouter imported from the uploadThing/next package and ensure that the key, imageUploader returns the file url.

We add a condition to the createUploadThing function that the file provided cannot exceed 4 MB in size.

Finally, we define two method types at this endpoint (GET and POST) for working with the UploadThing library:

GitHub GistTypeScript
import { createRouteHandler } from "uploadthing/next";
import { fileRouter } from "./core";

// Creating route handlers for Next.js API route
export const { GET, POST } = createRouteHandler({
  router: fileRouter
});
GET and POST methods defined for working with the file router

We can further extend this functionality to implement all the CRUD operations (create, read, update, and delete).

In fact, the UploadThing library consists of functions that can be used for updating, searching, and deleting objects.

You can figure out how to accomplish each of these operations on your own, but needless to say, the UploadThing is a versatile library that should be a part of every developer's knowledge stack.

We do away with the added bloat of things such as managing AWS IAM permissions, installing AWS SDK to programmatically operate an AWS S3 bucket.

There is no need to manage lifecycle rules, CORS/bucket access policies, and there is certainly no need to manage bucket region configuration, and setting a bucket visibility status.

UploadThing works great for small projects and for developers who want low infra overhead.

Conclusion

In this short article, we looked at the UploadThing library. We covered its key features using Next.js App router.

We setup a simple front-end and created a back-end file router for handling file uploads.

This article served as an introduction to this object storage library. Feel free to explore it in greater detail.

In the list below, you will find links to the official UploadThing docs and the GitHub repository used in this article:

I hope you found this article helpful and look forward to more in the future.

Thank you!

No Name

Abdullah Muhammad

Blogger. Software Engineer. Designer.

Subscribe to the newsletter

Get new articles, code samples, and project updates delivered straight to your inbox.