-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update FormData and route handlers examples #179
base: main
Are you sure you want to change the base?
Changes from 13 commits
f72add4
68de16b
6111ce3
1c3ec36
eee5bb0
a70957e
fe02e04
ff4ca67
510f096
177e7bf
716795c
e3e014f
28597b7
e11abf2
f637d17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
CLOUDINARY_CLOUD_NAME="" | ||
CLOUDINARY_API_KEY="" | ||
CLOUDINARY_UPLOADS_FOLDER="" | ||
CLOUDINARY_API_SECRET="" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"singleQuote": true, | ||
"jsxSingleQuote": true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,99 @@ | ||
# Uploading Files to Cloudinary with Next.js Route Handlers | ||
# Uploading Files to Cloudinary with Next.js API Routes/Route Handlers | ||
|
||
Coming soon | ||
This example shows how to upload files to Cloudinary using Next.js API Routes (Pages Router) and Route Handlers (App Router). | ||
|
||
## Getting Started | ||
|
||
### Using the pages router | ||
|
||
Create an API Route as shown in [upload.ts](src/pages/api/upload.ts) using the `formidable` library to accept files in request body. | ||
|
||
```ts | ||
const uploadsFolder = process.env.CLOUDINARY_UPLOADS_FOLDER; | ||
const form = new IncomingForm(); | ||
form.parse(req, async (err, fields, files) => { | ||
const result = await new Promise<UploadApiResponse>((resolve, reject) => { | ||
cloudinary.uploader.upload( | ||
file.filepath, | ||
{ resource_type: 'auto', folder: uploadsFolder }, | ||
(error, result) => { | ||
if (error || !result) reject(error); | ||
else resolve(result); | ||
}, | ||
); | ||
}); | ||
``` | ||
|
||
From the client side, you can use the `fetch` API to send a POST request to the API Route with the file in the request body as shown in [index.tsx](src/pages/index.tsx). | ||
|
||
```tsx | ||
// handleSubmit | ||
max-programming marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const formData = new FormData(); | ||
formData.append('file', file); | ||
const response = await fetch('/api/upload', { | ||
method: 'POST', | ||
body: formData, | ||
}); | ||
const result = await response.json(); | ||
|
||
// jsx | ||
return ( | ||
<form onSubmit={handleSubmit}> | ||
<input type='file' name='file' onChange={handleFileChange} /> | ||
<button type='submit'>Upload</button> | ||
</form> | ||
) | ||
``` | ||
|
||
### Using the app router | ||
|
||
Create a Route Handler as shown in [route.ts](src/app/app/api/upload/route.ts). Route Handlers don't require the `formidable` library to accept files in request body. | ||
|
||
```ts | ||
const uploadsFolder = process.env.CLOUDINARY_UPLOADS_FOLDER; | ||
const formData = await request.formData(); | ||
const file = formData.get('file') as File; | ||
const fileBuffer = await file.arrayBuffer(); | ||
|
||
const result = await new Promise<UploadApiResponse>((resolve, reject) => { | ||
cloudinary.uploader | ||
.upload_stream( | ||
{ resource_type: 'auto', folder: uploadsFolder }, | ||
(error, result) => { | ||
if (error || !result) reject(error); | ||
else resolve(result); | ||
}, | ||
) | ||
.end(Buffer.from(fileBuffer)); | ||
}); | ||
``` | ||
|
||
On the client side, you can use the `fetch` API to send a POST request to the Route Handler with the file in the request body. It should be a client component like shown in [Uploader.tsx](src/components/Uploader.tsx). Same as the pages router. | ||
|
||
## Running this example | ||
|
||
- Install the project dependencies with: | ||
```sh | ||
yarn install | ||
# or | ||
npm install | ||
``` | ||
|
||
- Add environment variables to a `.env.local` file: | ||
```sh | ||
CLOUDINARY_CLOUD_NAME="" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these shoulld have the NEXT_PUBLIC_ prefix onthe first 2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These variables aren't used in client side, so I removed |
||
CLOUDINARY_API_KEY="" | ||
CLOUDINARY_UPLOADS_FOLDER="" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it looks like throughout the examples you reference differently CLOUDINARY_UPLOADS_FOLDER vs NEXT_PUBLIC_CLOUDINARY_UPLOADS_FOLDER There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason I didn't put |
||
CLOUDINARY_API_SECRET="" | ||
``` | ||
|
||
> Note: the upload preset must be updated to one available in your account | ||
|
||
- Start the development server with: | ||
```sh | ||
yarn dev | ||
# or | ||
npm run dev | ||
``` | ||
|
||
- Visit the project at <http://localhost:3000>! |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import cloudinary from '@/lib/cloudinary'; | ||
import type { UploadApiResponse } from 'cloudinary'; | ||
|
||
export async function POST(request: Request) { | ||
try { | ||
const uploadsFolder = process.env.CLOUDINARY_UPLOADS_FOLDER; | ||
const formData = await request.formData(); | ||
const file = formData.get('file') as File; | ||
|
||
if (!file) { | ||
return Response.json({ error: 'No file uploaded' }, { status: 400 }); | ||
} | ||
|
||
const fileBuffer = await file.arrayBuffer(); | ||
const result = await new Promise<UploadApiResponse>((resolve, reject) => { | ||
cloudinary.uploader | ||
.upload_stream( | ||
{ resource_type: 'auto', folder: uploadsFolder }, | ||
(error, result) => { | ||
if (error || !result) reject(error); | ||
else resolve(result); | ||
}, | ||
) | ||
.end(Buffer.from(fileBuffer)); | ||
}); | ||
|
||
return Response.json({ secure_url: result.secure_url }); | ||
} catch (error) { | ||
console.error('Error uploading file:', error); | ||
return Response.json({ error: 'Error uploading file' }, { status: 500 }); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import Footer from '@/components/Footer'; | ||
import Uploader from '@/components/Uploader'; | ||
import type { Metadata } from 'next'; | ||
|
||
const TITLE = 'Cloudinary & Next.js Route Handlers'; | ||
export const metadata: Metadata = { | ||
title: TITLE, | ||
description: | ||
'Learn how to upload files from a form to Cloudinary using Next.js Route Handlers', | ||
}; | ||
|
||
export default function AppRouterPage() { | ||
return ( | ||
<main className='container mx-auto mb-5 text-center max-w-screen-lg space-y-5 px-4 py-0'> | ||
<h1 className='text-3xl font-semibold py-5'>{TITLE}</h1> | ||
<Uploader /> | ||
<Footer /> | ||
</main> | ||
); | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,20 @@ | ||
import './globals.css' | ||
import type { Metadata } from 'next' | ||
import { Inter } from 'next/font/google' | ||
import { Inter } from 'next/font/google'; | ||
import Header from '@/components/Header'; | ||
import '@/styles/globals.css'; | ||
|
||
import Nav from '@/components/Nav'; | ||
const inter = Inter({ | ||
subsets: ['latin'], | ||
display: 'swap', | ||
weight: ['400', '700'], | ||
}); | ||
|
||
const inter = Inter({ subsets: ['latin'] }) | ||
|
||
export const metadata: Metadata = { | ||
title: 'Cloudinary & Next.js Route Handlers', | ||
description: 'Learn how to upload files from a form to Cloudinary using Next.js Route Handlers', | ||
} | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
export default function RootLayout({ children }: React.PropsWithChildren) { | ||
return ( | ||
<html lang="en"> | ||
<html lang='en'> | ||
<body className={inter.className}> | ||
<div className="grid grid-rows-[auto_1fr] h-screen"> | ||
<Nav /> | ||
<main>{ children }</main> | ||
</div> | ||
<Header /> | ||
{children} | ||
</body> | ||
</html> | ||
) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have you thought about moving prettier to the root of the project?
that way we dont have to manage this in every single repo, it would be a bit cleaner and less to worry about in each example and inherit from the root of the project
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed there is some inconsistency between prettier configs in other projects. Like svelte, vue, etc
I will take the commons from all projects using prettier and put that in the root, and other settings like
prettier-plugin-svelte
will keep them in the individual filesDoes that work?