


Comparison of Spload feature between Documenso and aws-smage-upload example
Jan 03, 2025 am 01:41 AMIn 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.
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]) } }}
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) }
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 }) } }
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()
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>
Here getInputProps is returned useDropzone. Documenso uses react-dropzone.
import { useDropzone } from 'react-dropzone';
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} />
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); } };
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 }); };
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)); };
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, }; };
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 }; };
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.
References:
https://github.com/documenso/documenso/blob/main/packages/lib/universal/upload/put-file.ts#L69
https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/README.md
https://github.com/vercel/examples/tree/main/solutions/aws-s3-image-upload
https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/page.tsx#L58C5-L76C12
https://github.com/vercel/examples/blob/main/solutions/aws-s3-image-upload/app/api/upload/route.ts
https://github.com/documenso/documenso/blob/main/packages/ui/primitives/document-dropzone.tsx#L157
https://react-dropzone.js.org/
https://github.com/documenso/documenso/blob/main/apps/web/src/app/(dashboard)/documents/upload-document.tsx#L61
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!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Java and JavaScript are different programming languages, each suitable for different application scenarios. Java is used for large enterprise and mobile application development, while JavaScript is mainly used for web page development.

JavaScriptcommentsareessentialformaintaining,reading,andguidingcodeexecution.1)Single-linecommentsareusedforquickexplanations.2)Multi-linecommentsexplaincomplexlogicorprovidedetaileddocumentation.3)Inlinecommentsclarifyspecificpartsofcode.Bestpractic

PlacingtagsatthebottomofablogpostorwebpageservespracticalpurposesforSEO,userexperience,anddesign.1.IthelpswithSEObyallowingsearchenginestoaccesskeyword-relevanttagswithoutclutteringthemaincontent.2.Itimprovesuserexperiencebykeepingthefocusonthearticl

The following points should be noted when processing dates and time in JavaScript: 1. There are many ways to create Date objects. It is recommended to use ISO format strings to ensure compatibility; 2. Get and set time information can be obtained and set methods, and note that the month starts from 0; 3. Manually formatting dates requires strings, and third-party libraries can also be used; 4. It is recommended to use libraries that support time zones, such as Luxon. Mastering these key points can effectively avoid common mistakes.

JavaScriptispreferredforwebdevelopment,whileJavaisbetterforlarge-scalebackendsystemsandAndroidapps.1)JavaScriptexcelsincreatinginteractivewebexperienceswithitsdynamicnatureandDOMmanipulation.2)Javaoffersstrongtypingandobject-orientedfeatures,idealfor

Event capture and bubble are two stages of event propagation in DOM. Capture is from the top layer to the target element, and bubble is from the target element to the top layer. 1. Event capture is implemented by setting the useCapture parameter of addEventListener to true; 2. Event bubble is the default behavior, useCapture is set to false or omitted; 3. Event propagation can be used to prevent event propagation; 4. Event bubbling supports event delegation to improve dynamic content processing efficiency; 5. Capture can be used to intercept events in advance, such as logging or error processing. Understanding these two phases helps to accurately control the timing and how JavaScript responds to user operations.

JavaScripthassevenfundamentaldatatypes:number,string,boolean,undefined,null,object,andsymbol.1)Numbersuseadouble-precisionformat,usefulforwidevaluerangesbutbecautiouswithfloating-pointarithmetic.2)Stringsareimmutable,useefficientconcatenationmethodsf

If JavaScript applications load slowly and have poor performance, the problem is that the payload is too large. Solutions include: 1. Use code splitting (CodeSplitting), split the large bundle into multiple small files through React.lazy() or build tools, and load it as needed to reduce the first download; 2. Remove unused code (TreeShaking), use the ES6 module mechanism to clear "dead code" to ensure that the introduced libraries support this feature; 3. Compress and merge resource files, enable Gzip/Brotli and Terser to compress JS, reasonably merge files and optimize static resources; 4. Replace heavy-duty dependencies and choose lightweight libraries such as day.js and fetch
