Trying to create API Route for uploading files. Works locally. Fails on Vercel Deployment.
Answered
Pixiebob posted this in #help-forum
PixiebobOP
I'm trying to create an API route where I upload a file from my system to cloudinary.
I've tried different approaches. I've used multipart/form-data solution, I've tried to use server actions to do it. I've converted the buffer on the frontend into uint8Array and then into an array which translates to the normal API application/json approach. No matter how I try to piece it together, it always fails on Vercel Deployment.
I've searched up solutions to the problem online but couldn't find one except a downvoted solution which suggested to downgrade to Next 13 from Next 14. But I cannot that since Next 14 has stable server actions which are being used in the application quite well.
I've tried different approaches. I've used multipart/form-data solution, I've tried to use server actions to do it. I've converted the buffer on the frontend into uint8Array and then into an array which translates to the normal API application/json approach. No matter how I try to piece it together, it always fails on Vercel Deployment.
I've searched up solutions to the problem online but couldn't find one except a downvoted solution which suggested to downgrade to Next 13 from Next 14. But I cannot that since Next 14 has stable server actions which are being used in the application quite well.
Answered by Pixiebob
import cloudinary from "@/lib/cloudinary";
import { auth } from "@clerk/nextjs";
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
try {
const { userId } = auth();
if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}
const data = await req.formData();
const file: File = data.get("file") as unknown as File;
if (!file) {
return new NextResponse("No file provided", { status: 400 });
}
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const base64String: string = buffer.toString("base64");
const cloudinaryFile = await cloudinary.uploader.upload(
`data:image/png;base64,${base64String}`,
{
folder: "users/" + "test" + "/reference/logo/",
},
);
if (cloudinaryFile instanceof Error || cloudinaryFile == undefined) {
return new NextResponse("Internal Server Error", { status: 500 });
}
return new NextResponse("File uploaded successfully", { status: 200 });
} catch (err) {
return new NextResponse("Internal Server Error", { status: 500 });
}
}26 Replies
PixiebobOP
My current code for the API route is the following:
import cloudinary from "@/lib/cloudinary";
import prismadb from "@/lib/prismadb";
import { auth } from "@clerk/nextjs";
import { UploadApiErrorResponse, UploadApiResponse } from "cloudinary";
import { NextRequest, NextResponse } from "next/server";
import { createReadStream } from "streamifier";
export async function POST(req: NextRequest) {
try {
const { userId } = auth();
const {
description,
fileArray,
}: { description: string; fileArray: number[] } = await req.json();
if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}
if (!description || !fileArray) {
return new NextResponse("Bad Request", { status: 400 });
}
const uint8Array = new Uint8Array(fileArray);
const buffer = Buffer.from(uint8Array);
const cloudinaryFile:
| UploadApiResponse
| UploadApiErrorResponse
| undefined = await new Promise((resolve, reject) => {
let cld_upload_stream = cloudinary.uploader.upload_stream(
{
folder: "users/" + userId + "/reference/logo/",
},
(error, result) => {
if (error == undefined) {
resolve(result);
} else {
reject(new Error("Something went wrong with Cloudinary"));
}
},
);
createReadStream(buffer).pipe(cld_upload_stream);
});
if (cloudinaryFile instanceof Error || cloudinaryFile == undefined) {
return new NextResponse("Internal Server Error", { status: 500 });
}
// ... rest of the action
return new NextResponse("File uploaded successfully", { status: 200 });
} catch {
return new NextResponse("Internal Server Error", { status: 500 });
}
}It gives a 405 on deployment but works locally.
Code for server action:
export async function uploadFileToCloudinary(array: number[]) {
try {
const { userId } = auth();
if (!userId) {
throw new Error("Unauthorized");
}
const uint8Array = new Uint8Array(array);
const buffer = Buffer.from(uint8Array);
const response: UploadApiResponse | UploadApiErrorResponse | undefined =
await new Promise((resolve, reject) => {
let cld_upload_stream = cloudinary.uploader.upload_stream(
{
folder: "users/" + userId + "/reference/logo/",
},
(error, result) => {
if (error == undefined) {
resolve(result);
} else {
reject(new Error("Something went wrong with Cloudinary"));
}
},
);
createReadStream(buffer).pipe(cld_upload_stream);
});
return response;
} catch {
throw new Error("Failed to fetch database");
}
}Server action also works locally but fails on deployment.
if you want to upload a file, it's best to use
FormData. post your (not working) code using FormData herePixiebobOP
same result
i know. send the code
PixiebobOP
for the form data?
okays
@joulev i know. send the code
PixiebobOP
I apologize but I misunderstood. I changed my code alot since my first attempt was using FormData. I'll get back to this in a few minutes.
@joulev i know. send the code
PixiebobOP
import cloudinary from "@/lib/cloudinary";
import { auth } from "@clerk/nextjs";
import { UploadApiErrorResponse, UploadApiResponse } from "cloudinary";
import { NextRequest, NextResponse } from "next/server";
import { createReadStream } from "streamifier";
export async function POST(req: NextRequest) {
try {
const { userId } = auth();
if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}
const data = await req.formData();
const file: File = data.get("file") as unknown as File;
if (!file) {
return new NextResponse("No file provided", { status: 400 });
}
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const cloudinaryFile:
| UploadApiResponse
| UploadApiErrorResponse
| undefined = await new Promise((resolve, reject) => {
let cld_upload_stream = cloudinary.uploader.upload_stream(
{
folder: "users/" + "test" + "/reference/logo/",
},
(error, result) => {
if (error == undefined) {
resolve(result);
} else {
reject(new Error("Something went wrong with Cloudinary"));
}
},
);
createReadStream(buffer).pipe(cld_upload_stream);
});
if (cloudinaryFile instanceof Error || cloudinaryFile == undefined) {
return new NextResponse("Internal Server Error", { status: 500 });
}
return new NextResponse("File uploaded successfully", { status: 200 });
} catch {
return new NextResponse("Internal Server Error", { status: 500 });
}
}Frontend:
async function onSubmitLogo(values: z.infer<typeof formSchema>) {
// Do something with the form values.
// ✅ This will be type-safe and validated.
toast("Uploading file...");
try {
const file: File = values.files[0];
const data = new FormData();
data.append("file", file);
const response = await axios.post(
"/api/settings/business/referenceformdata",
data,
);
toast(response.data);
queryClient.invalidateQueries({ queryKey: "logo" });
formLogo.reset();
} catch {
toast("File upload failed");
}
}Works in local environment.
405 method not allowed on production.
:method:POST
:path:/api/settings/business/referenceformdata
:scheme:https
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate, br
Accept-Language:en-US,en;q=0.9
Content-Length:487936
Content-Type:multipart/form-data; boundary=----WebKitFormBoundarypzYkhDC1z3XcEyBP@Pixiebob js
import cloudinary from "@/lib/cloudinary";
import { auth } from "@clerk/nextjs";
import { UploadApiErrorResponse, UploadApiResponse } from "cloudinary";
import { NextRequest, NextResponse } from "next/server";
import { createReadStream } from "streamifier";
export async function POST(req: NextRequest) {
try {
const { userId } = auth();
if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}
const data = await req.formData();
const file: File = data.get("file") as unknown as File;
if (!file) {
return new NextResponse("No file provided", { status: 400 });
}
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const cloudinaryFile:
| UploadApiResponse
| UploadApiErrorResponse
| undefined = await new Promise((resolve, reject) => {
let cld_upload_stream = cloudinary.uploader.upload_stream(
{
folder: "users/" + "test" + "/reference/logo/",
},
(error, result) => {
if (error == undefined) {
resolve(result);
} else {
reject(new Error("Something went wrong with Cloudinary"));
}
},
);
createReadStream(buffer).pipe(cld_upload_stream);
});
if (cloudinaryFile instanceof Error || cloudinaryFile == undefined) {
return new NextResponse("Internal Server Error", { status: 500 });
}
return new NextResponse("File uploaded successfully", { status: 200 });
} catch {
return new NextResponse("Internal Server Error", { status: 500 });
}
}
on quick look it looks ok. can you make a full [minimal reproduction repository](https://nextjs-faq.com/minimal-reproduction-repository)? so i can clone and try it myself
remember: minimal. remove irrelevant stuff. remove cloudfinary integration, just
console.log'ing the file on the server is enough@joulev remember: minimal. remove irrelevant stuff. remove cloudfinary integration, just `console.log`'ing the file on the server is enough
PixiebobOP
yeah sure but it'll work on local environment, just a heads up
yes i know
@joulev yes i know
PixiebobOP
should I remove cloudinary? I have to use cloudinary to upload the file
https://nextjs-forum.com/post/1189278797791506453#message-1189278797791506453
similar to this, could you try it with api route in pages router? or does cloudinary work in async/await without using callback?
similar to this, could you try it with api route in pages router? or does cloudinary work in async/await without using callback?
@Pixiebob should I remove cloudinary? I have to use cloudinary to upload the file
make the thing minimal. i just need the file to show up inside your route handler/api route. how you handle that file server-side (e.g., upload to cloudinary, upload to s3) is not relevant to this question is it?
@joulev make the thing minimal. i just need the file to show up inside your route handler/api route. how you handle that file server-side (e.g., upload to cloudinary, upload to s3) is not relevant to this question is it?
PixiebobOP
Hey man, I am very much thankful to you but I solved my own problem, maybe in not a very best practice way. I really appreciate the time you took to look at my code and your will to test it out yourself.
PixiebobOP
import cloudinary from "@/lib/cloudinary";
import { auth } from "@clerk/nextjs";
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
try {
const { userId } = auth();
if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}
const data = await req.formData();
const file: File = data.get("file") as unknown as File;
if (!file) {
return new NextResponse("No file provided", { status: 400 });
}
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const base64String: string = buffer.toString("base64");
const cloudinaryFile = await cloudinary.uploader.upload(
`data:image/png;base64,${base64String}`,
{
folder: "users/" + "test" + "/reference/logo/",
},
);
if (cloudinaryFile instanceof Error || cloudinaryFile == undefined) {
return new NextResponse("Internal Server Error", { status: 500 });
}
return new NextResponse("File uploaded successfully", { status: 200 });
} catch (err) {
return new NextResponse("Internal Server Error", { status: 500 });
}
}Answer
nice, glad it worked
PixiebobOP
This solution works on production too. I guess streaming it to cloudinary was the real problem and was not allowed on serverless functions.