Better Upload Documentation

repository·main·Indexed 23 days ago

https://github.com/nic13gamer/better-upload

A React-focused library for simplifying direct-to-S3 file uploads to any S3-compatible service. It provides client-side tools like the useUploadFiles hook and UploadDropzone component, and a server-side Router for managing upload routes, constraints, and authentication. Compatible with Next.js, TanStack Start, Remix, Express, Fastify, Hono, and Elysia, with built-in clients for AWS, Cloudflare R2, Tigris, Backblaze B2, DigitalOcean Spaces, Wasabi, MinIO, and Linode.

Tokens
28.4K
Snippets
96
Records
129
Agent score
78%

What's inside Better Upload

  1. Overview of Better Upload

    main
    Better Upload provides simple and easy file upload capabilities for React applications. It allows users to upload files directly to any S3-compatible storage service with minimal configuration. It is designed to work with any React-based framework, including Next.js and Tanstack Start.
  2. Understand the documentation project structure

    main

    The documentation site is built using Next.js and Fumadocs. Key files and routes include:

    • lib/source.ts: Contains the code for the content source adapter. The loader() function provides the interface to access your content.
    • app/layout.config.tsx: Contains shared options for layouts.
    • app/(home): Route group for the landing page and other top-level pages.
    • app/docs: The main documentation layout and pages.
    • app/api/search/route.ts: The Route Handler responsible for search functionality.
  3. Manage multipart uploads

    main

    For large files, use the multipart upload helpers to break the upload into parts. The workflow is:

    1. createMultipartUpload: Initialize the upload and get an uploadId.
    2. uploadPart: Upload individual chunks of data.
    3. completeMultipartUpload: Finalize the upload by providing an array of partNumber and eTag pairs.
    4. abortMultipartUpload: Cancel the upload if necessary.
    import {
      createMultipartUpload,
      uploadPart,
      completeMultipartUpload,
      abortMultipartUpload
    } from '@better-upload/server/helpers';
    
    // 1. Create
    const { uploadId } = await createMultipartUpload(s3, {
      bucket: 'my-bucket',
      key: 'large-file.zip',
      contentType: 'application/zip',
    });
    
    // 2. Upload part
    const { eTag } = await uploadPart(s3, {
      bucket: 'my-bucket',
      key: 'large-file.zip',
      uploadId: '...',
      partNumber: 1,
      body: partData,
    });
    
    // 3. Complete
    await completeMultipartUpload(s3, {
      bucket: 'my-bucket',
      key: 'large-file.zip',
      uploadId: '...',
      parts: [
        { partNumber: 1, eTag: '...' },
        { partNumber: 2, eTag: '...' },
      ],
    });
    
    // Or 4. Abort
    await abortMultipartUpload(s3, {
      bucket: 'my-bucket',
      key: 'large-file.zip',
      uploadId: '...',
    });
  4. Handle upload lifecycle events with useUploadFile

    main

    You can intercept various stages of the upload process by providing callback functions in the useUploadFile options object:

    • onBeforeUpload: Called before requesting the pre-signed URL. Use this to modify the file (e.g., resizing, renaming) by returning a new File object, or throw an error to reject the upload.
    • onUploadBegin: Called after the server responds with the pre-signed URL, but before the file starts uploading to S3.
    • onUploadProgress: Called whenever the upload progress changes. Provides { file } where file.progress is a value between 0 and 1.
    • onUploadComplete: Called after a successful upload.
    • onError: Called if the upload fails or if inputs are invalid. Provides the error object.
    • onUploadSettle: Called after the upload finishes, regardless of whether it succeeded or failed.
    useUploadFile({
      route: 'profile',
      onBeforeUpload: ({ file }) => {
        // rename the file
        return new File([file], 'renamed-' + file.name, { type: file.type });
      },
      onUploadBegin: ({ file, metadata }) => {
        console.log('Upload begin');
      },
      onUploadProgress: ({ file }) => {
        console.log(`Upload progress: ${file.progress * 100}%`);
      },
      onUploadComplete: ({ file, metadata }) => {
        console.log('File uploaded');
      },
      onError: (error) => {
        console.log(error.message);
      },
      onUploadSettle: ({ file, metadata }) => {
        console.log('Upload settled');
      },
    });
  5. Configure the `@better-upload/server` Router

    main

    The Router object is the core configuration for your upload server. It defines the storage client, the target bucket, and a set of upload routes. Each route can have specific constraints like allowed file types, whether multiple files are permitted, and a maximum number of files.

    Key properties of the Router object:

    • client: An S3-compatible client (e.g., aws(), cloudflare(), backblaze(), tigris()).
    • bucketName: The name of the destination bucket.
    • routes: An object where keys are route names and values are configurations created via route().
    import { route, type Router } from '@better-upload/server';
    import { aws } from '@better-upload/server/clients';
    
    const router: Router = {
      client: aws(),
      bucketName: 'my-bucket',
      routes: {
        images: route({
          fileTypes: ['image/*'],
          multipleFiles: true,
          maxFiles: 4,
        }),
      },
    };
  6. Quickstart: Single file uploads with Better Upload

    main

    This guide demonstrates how to set up single file uploads in a React application using Better Upload. The process involves installing the server and client packages, configuring a server route to generate pre-signed S3 URLs, and using the @better-upload/client hooks with a pre-built UI component.

    Prerequisites:

    • An S3-compatible bucket (AWS S3, Cloudflare R2, etc.).
    • A React framework (e.g., Next.js).

    Steps:

    1. Install dependencies:

      npm i @better-upload/server @better-upload/client
    2. Set up the server: Configure a server route (e.g., profile) to generate pre-signed URLs for your S3 bucket.

    3. Install UI components: Use the shadcn CLI to add the pre-built upload button:

      npx shadcn@latest add @better-upload/upload-button
    4. Implement the Uploader component: Use the useUploadFile hook to connect your UI to the server route.

    5. Configure CORS: Ensure your S3 bucket has the correct CORS configuration to allow requests from your application's domain.

    'use client'; // only for Next.js
    
    import { useUploadFile } from '@better-upload/client';
    import { UploadButton } from '@/components/ui/upload-button';
    
    export function Uploader() {
      const { control } = useUploadFile({
        route: 'profile',
      });
    
      return <UploadButton control={control} accept="image/*" />;
    }
  7. Setup an upload route for TanStack Form

    main

    Configure your server-side upload route using @better-upload/server. In this example, we use the Next.js adapter and AWS client. The route configuration defines the constraints for the upload, such as multipleFiles, maxFiles, and maxFileSize. You can also use onBeforeUpload to customize the object key (e.g., adding a prefix like form/).

    import { route, type Router } from '@better-upload/server';
    import { toRouteHandler } from '@better-upload/server/adapters/next';
    import { aws } from '@better-upload/server/clients';
    
    const router: Router = {
      client: aws(),
      bucketName: 'my-bucket',
      routes: {
        form: route({
          multipleFiles: true,
          maxFiles: 5,
          maxFileSize: 1024 * 1024 * 5, // 5MB
          onBeforeUpload() {
            return {
              generateObjectInfo: ({ file }) => ({ key: `form/${file.name}` }),
            };
          },
        }),
      },
    };
    
    export const { POST } = toRouteHandler(router);
  8. Integrate `@better-upload/server` with Next.js

    main

    To use Better Upload in a Next.js App Router environment, use the toRouteHandler adapter from @better-upload/server/adapters/next. This converts your Router configuration into a standard Next.js Route Handler.

    import { route, type Router } from '@better-upload/server';
    import { toRouteHandler } from '@better-upload/server/adapters/next';
    import { aws } from '@better-upload/server/clients';
    
    const router: Router = {
      client: aws(),
      bucketName: 'my-bucket',
      routes: {
        images: route({
          fileTypes: ['image/*'],
          multipleFiles: true,
          maxFiles: 4,
        }),
      },
    };
    
    export const { POST } = toRouteHandler(router);
  9. Use TanStack Query with Better Upload

    main

    If you prefer not to use the built-in Better Upload hooks, you can integrate the library with TanStack Query by using the uploadFile (for single files) or uploadFiles (for multiple files) functions inside a useMutation hook. This allows you to leverage TanStack Query's mutation lifecycle (onSuccess, onError, isPending) for your upload logic.

    import { uploadFiles } from '@better-upload/client';
    import { useMutation } from '@tanstack/react-query';
    
    // Inside your component
    const { mutate: upload, isPending } = useMutation({
      mutationFn: async (files: File[]) => {
        return uploadFiles({
          files,
          route: 'form',
          onFileStateChange: ({ file }) => {
            // handle progress
          },
        });
      },
      onSuccess: ({ files, failedFiles, metadata }) => {
        // handle success
      },
      onError: (error) => {
        // handle error
      },
    });
  10. Install the UploadProgress component

    main

    You can install the UploadProgress component using the shadcn CLI or manually.

    Via CLI

    Run the following command to add the component directly:

    npx shadcn@latest add @better-upload/upload-progress

    Manual Installation

    1. Install the required dependencies:
    npm i lucide-react react-dropzone
    1. Ensure you have shadcn/ui configured in your project with the progress component installed.
    2. Copy the component source code into your project and update the import paths to match your local directory structure.