Home > Web Front-end > JS Tutorial > Comparison of Spload feature between Documenso and aws-smage-upload example

Comparison of Spload feature between Documenso and aws-smage-upload example

Mary-Kate Olsen
Release: 2025-01-03 01:41:39
Original
497 people have browsed it

In this article, we will compare the steps involved to upload a file to AWS S3 between Documenso and AWS S3 image upload example.

We start with the simple example provided by Vercel.

Comparison of Spload feature between Documenso and aws-smage-upload example

examples/aws-s3-image-upload

Vercel provides a good working example of uploading a file to AWS S3.

This example’s README provides two options, either you can use an existing S3 bucket or create a new bucket. Understanding this helps

you configure your upload feature correctly.

It is about the time we look at the source code. We are looking for an input element with type=file. In the app/page.tsx, you will find this below code:

return (
    <main>
      <h1>Upload a File to S3</h1>
      <form onSubmit={handleSubmit}>
        <input
         >



<h2>
  
  
  <strong>onChange</strong>
</h2>

<p>onChange updates state using setFile, but it does not do the uploading. upload happens when you submit this form.<br>
</p>

<pre class="brush:php;toolbar:false">onChange={(e) => {
  const files = e.target.files
  if (files) {
    setFile(files[0])
  }
}}
Copy after login

handleSubmit

A lot is happening in the handleSubmit function. We need to analyse the list of operations in this handleSubmit function. I have written the comments inside this code snippet to explain the steps.

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()

    if (!file) {
      alert('Please select a file to upload.')
      return
    }

    setUploading(true)

    const response = await fetch(
      process.env.NEXT_PUBLIC_BASE_URL + '/api/upload',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ filename: file.name, contentType: file.type }),
      }
    )

    if (response.ok) {
      const { url, fields } = await response.json()

      const formData = new FormData()
      Object.entries(fields).forEach(([key, value]) => {
        formData.append(key, value as string)
      })
      formData.append('file', file)

      const uploadResponse = await fetch(url, {
        method: 'POST',
        body: formData,
      })

      if (uploadResponse.ok) {
        alert('Upload successful!')
      } else {
        console.error('S3 Upload Error:', uploadResponse)
        alert('Upload failed.')
      }
    } else {
      alert('Failed to get pre-signed URL.')
    }

    setUploading(false)
  }
Copy after login

api/upload

api/upload/route.ts has the below code:

import { createPresignedPost } from '@aws-sdk/s3-presigned-post'
import { S3Client } from '@aws-sdk/client-s3'
import { v4 as uuidv4 } from 'uuid'

export async function POST(request: Request) {
  const { filename, contentType } = await request.json()

  try {
    const client = new S3Client({ region: process.env.AWS_REGION })
    const { url, fields } = await createPresignedPost(client, {
      Bucket: process.env.AWS_BUCKET_NAME,
      Key: uuidv4(),
      Conditions: [
        ['content-length-range', 0, 10485760], // up to 10 MB
        ['starts-with', '$Content-Type', contentType],
      ],
      Fields: {
        acl: 'public-read',
        'Content-Type': contentType,
      },
      Expires: 600, // Seconds before the presigned post expires. 3600 by default.
    })

    return Response.json({ url, fields })
  } catch (error) {
    return Response.json({ error: error.message })
  }
}
Copy after login

The first request in the handleSubmit was to /api/upload and sends content type and filename as payload. It is parsed as below:

const { filename, contentType } = await request.json()
Copy after login

Next step is to create a S3 client and then create a presigned post that returns url and field. You would use this url to upload your file.

With this knowledge, let’s analyse how the upload works in Documenso and draw some comparison.

PDF file upload in Documenso

Let’s start with the input element with type=file. Code is organized differently in Documenso. You would find input element in a file named document-dropzone.tsx.

<input {...getInputProps()} />

<p className="text-foreground mt-8 font-medium">{_(heading[type])}</p>
Copy after login

Here getInputProps is returned useDropzone. Documenso uses react-dropzone.

import { useDropzone } from 'react-dropzone';
Copy after login

onDrop calls props.onDrop, you will find an attribute value named onFileDrop in upload-document.tsx.

<DocumentDropzone
  className="h-[min(400px,50vh)]"
  disabled={remaining.documents === 0 || !session?.user.emailVerified}
  disabledMessage={disabledMessage}
  onDrop={onFileDrop}
  onDropRejected={onFileDropRejected}
/>
Copy after login

Let’s see what happens in onFileDrop function.

const onFileDrop = async (file: File) => {
    try {
      setIsLoading(true);

      const { type, data } = await putPdfFile(file);

      const { id: documentDataId } = await createDocumentData({
        type,
        data,
      });

      const { id } = await createDocument({
        title: file.name,
        documentDataId,
        teamId: team?.id,
      });

      void refreshLimits();

      toast({
        title: _(msg`Document uploaded`),
        description: _(msg`Your document has been uploaded successfully.`),
        duration: 5000,
      });

      analytics.capture('App: Document Uploaded', {
        userId: session?.user.id,
        documentId: id,
        timestamp: new Date().toISOString(),
      });

      router.push(`${formatDocumentsPath(team?.url)}/${id}/edit`);
    } catch (err) {
      const error = AppError.parseError(err);

      console.error(err);

      if (error.code === 'INVALID_DOCUMENT_FILE') {
        toast({
          title: _(msg`Invalid file`),
          description: _(msg`You cannot upload encrypted PDFs`),
          variant: 'destructive',
        });
      } else if (err instanceof TRPCClientError) {
        toast({
          title: _(msg`Error`),
          description: err.message,
          variant: 'destructive',
        });
      } else {
        toast({
          title: _(msg`Error`),
          description: _(msg`An error occurred while uploading your document.`),
          variant: 'destructive',
        });
      }
    } finally {
      setIsLoading(false);
    }
  };
Copy after login

There’s a lot of happening but for our analysis, let’s only consider the function named putFile.

putPdfFile

putPdfFile is defined in upload/put-file.ts

/**
 * Uploads a document file to the appropriate storage location and creates
 * a document data record.
 */
export const putPdfFile = async (file: File) => {
  const isEncryptedDocumentsAllowed = await getFlag('app_allow_encrypted_documents').catch(
    () => false,
  );

  const pdf = await PDFDocument.load(await file.arrayBuffer()).catch((e) => {
    console.error(`PDF upload parse error: ${e.message}`);

    throw new AppError('INVALID_DOCUMENT_FILE');
  });

  if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
    throw new AppError('INVALID_DOCUMENT_FILE');
  }

  if (!file.name.endsWith('.pdf')) {
    file.name = `${file.name}.pdf`;
  }

  removeOptionalContentGroups(pdf);

  const bytes = await pdf.save();

  const { type, data } = await putFile(new File([bytes], file.name, { type: 'application/pdf' }));

  return await createDocumentData({ type, data });
};
Copy after login

putFile

This calls putFile function.

/**
 * Uploads a file to the appropriate storage location.
 */
export const putFile = async (file: File) => {
  const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');

  return await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
    .with('s3', async () => putFileInS3(file))
    .otherwise(async () => putFileInDatabase(file));
};
Copy after login

putFileInS3

const putFileInS3 = async (file: File) => {
  const { getPresignPostUrl } = await import('./server-actions');

  const { url, key } = await getPresignPostUrl(file.name, file.type);

  const body = await file.arrayBuffer();

  const reponse = await fetch(url, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/octet-stream',
    },
    body,
  });

  if (!reponse.ok) {
    throw new Error(
      `Failed to upload file "${file.name}", failed with status code ${reponse.status}`,
    );
  }

  return {
    type: DocumentDataType.S3_PATH,
    data: key,
  };
};
Copy after login

getPresignPostUrl

export const getPresignPostUrl = async (fileName: string, contentType: string) => {
  const client = getS3Client();

  const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');

  let token: JWT | null = null;

  try {
    const baseUrl = APP_BASE_URL() ?? 'http://localhost:3000';

    token = await getToken({
      req: new NextRequest(baseUrl, {
        headers: headers(),
      }),
    });
  } catch (err) {
    // Non server-component environment
  }

  // Get the basename and extension for the file
  const { name, ext } = path.parse(fileName);

  let key = `${alphaid(12)}/${slugify(name)}${ext}`;

  if (token) {
    key = `${token.id}/${key}`;
  }

  const putObjectCommand = new PutObjectCommand({
    Bucket: process.env.NEXT_PRIVATE_UPLOAD_BUCKET,
    Key: key,
    ContentType: contentType,
  });

  const url = await getSignedUrl(client, putObjectCommand, {
    expiresIn: ONE_HOUR / ONE_SECOND,
  });

  return { key, url };
};
Copy after login

Comparison

  • You do not see any POST request in Documenso. It uses a function named getSignedUrl to get the url, whereas

    vercel example makes a POST request to api/upload route.

  • Input element can be located easily in Vercel example as this is just an example, but Documenso is found

    to be using react-dropzone and the input element is located according to business context.

About us:

At Thinkthroo, we study large open source projects and provide architectural guides. We have developed reusable Components, built with tailwind, that you can use in your project.

We offer Next.js, React and Node development services.

Book a meeting with us to discuss your project.

Comparison of Spload feature between Documenso and aws-smage-upload example

References:

  1. https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L69

  2. https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/README.md

  3. https://github.com/vercel/examples/tree/main/solutions/aws-s3-image-upload

  4. https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/page.tsx#L58C5-L76C12

  5. https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/api/upload/route.ts

  6. https://github.com/documenso/documenso/blob/main/packages/ui/primitives/document-dropzone.tsx#L157

  7. https://react-dropzone.js.org/

  8. https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upload-document.tsx#L61

  9. https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L22

The above is the detailed content of Comparison of Spload feature between Documenso and aws-smage-upload example. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template