Skip to content
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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/nextjs-route-handlers-upload/.env.example
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=""
4 changes: 4 additions & 0 deletions examples/nextjs-route-handlers-upload/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
Copy link
Collaborator

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

Copy link
Contributor Author

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 files

Does that work?

"singleQuote": true,
"jsxSingleQuote": true
}
100 changes: 98 additions & 2 deletions examples/nextjs-route-handlers-upload/README.md
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=""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these shoulld have the NEXT_PUBLIC_ prefix onthe first 2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables aren't used in client side, so I removed NEXT_PUBLIC_ from them

CLOUDINARY_API_KEY=""
CLOUDINARY_UPLOADS_FOLDER=""
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I didn't put NEXT_PUBLIC_ in these is because these env variables are not used on the client side, so it's better if they are not exposed

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>!
32 changes: 18 additions & 14 deletions examples/nextjs-route-handlers-upload/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@
"lint": "next lint"
},
"dependencies": {
"@types/node": "20.6.2",
"@types/react": "18.2.22",
"@types/react-dom": "18.2.7",
"autoprefixer": "10.4.15",
"cloudinary": "^1.41.0",
"eslint": "8.49.0",
"eslint-config-next": "13.4.19",
"next": "13.4.19",
"next-cloudinary": "^4.23.1",
"postcss": "8.4.29",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "3.3.3",
"typescript": "5.2.2"
"autoprefixer": "10.4.20",
"cloudinary": "^2.4.0",
"formidable": "^3.5.1",
"next": "14.2.11",
"next-cloudinary": "^6.13.0",
"postcss": "8.4.47",
"react": "18.3.1",
"react-dom": "18.3.1",
"tailwindcss": "3.4.11",
"typescript": "5.6.2"
},
"devDependencies": {
"@types/formidable": "^3.4.5",
"@types/node": "22.5.5",
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0",
"eslint": "9.10.0",
"eslint-config-next": "14.2.11"
}
}

This file was deleted.

29 changes: 0 additions & 29 deletions examples/nextjs-route-handlers-upload/src/app/api/upload/route.ts

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 });
}
}
20 changes: 20 additions & 0 deletions examples/nextjs-route-handlers-upload/src/app/app/page.tsx
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>
);
}
20 changes: 0 additions & 20 deletions examples/nextjs-route-handlers-upload/src/app/globals.css

This file was deleted.

35 changes: 13 additions & 22 deletions examples/nextjs-route-handlers-upload/src/app/layout.tsx
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>
)
);
}
Loading