I have implemented a file downloading api endpoint in next js. It lets the user download files from secluded folders that are within the public folder of next JS. Here is my code:
export async function GET(request: Request, { params }: GetParams) {
try {
const filename = params.filename;
if (!filename) {
throw new Error('filename parameter should not be empty.');
}
let folderPath = '';
if (filename.startsWith('myFile')) {
folderPath = 'public/myFiles';
} else if (filename.startsWith('yourFile')) {
folderPath = 'public/yourFiles';
} else if (filename.startsWith('theirFile')) {
folderPath = 'public/theirFiles';
} else {
throw new Error(`Unknown filename type: ${filename}`);
}
const files = await fs.promises.readdir(
path.join(process.cwd(), folderPath)
);
for (const file of files) {
if (file.startsWith(filename)) {
const buffer = await readFile(
path.join(process.cwd(), folderPath, file)
);
//Create headers
const headers = new Headers();
headers.append('content-disposition', `attachment; filename="${file}"`);
headers.append('Content-Type', 'image/png');
return new Response(buffer, {
status: 200,
headers,
});
}
}
return new Response(`File not found: ${filename}`, {
status: 404,
});
} catch (error: any) {
console.log(error);
return new Response(`Internal server error`, {
status: 500,
});
}
}
This works perfect in dev environment. However this fails in production with the error:
[Error: ENOENT: no such file or directory, scandir '/var/task/public/myFiles'] { errno: -2, code: 'ENOENT', syscall: 'scandir', path: '/var/task/public/myFiles' }
Clearly either,
path
?).Search on the web is either filled with old "page router" references, or they have some weird concepts that confuses further. Is there a way to see the directory structure that vercel uses for deploying the application? Any pointer or help is appreciated.
I was able to solve this using the following post:
Here is the code that works in prod mode on vercel:
export async function GET(request: Request, { params }: GetParams) {
try {
const filename = params.filename;
if (!filename) {
throw new Error('filename parameter should not be empty.');
}
let folderPath = '';
if (filename.startsWith('myFile')) {
folderPath = path.resolve('./public', 'myFiles');
} else if (filename.startsWith('yourFile')) {
folderPath = path.resolve('./public', 'yourFiles');
} else if (filename.startsWith('theirFile')) {
folderPath = path.resolve('./public', 'theirFiles');
} else {
throw new Error(`Unknown filename type: ${filename}`);
}
const files = await fs.promises.readdir(path.join(folderPath));
for (const file of files) {
if (file.startsWith(filename)) {
const buffer = await readFile(path.join(folderPath, file));
//Create headers
const headers = new Headers();
headers.append('content-disposition', `attachment; filename="${file}"`);
headers.append('Content-Type', 'image/png');
return new Response(buffer, {
status: 200,
headers,
});
}
}
return new Response(`File not found: ${filename}`, {
status: 404,
});
} catch (error: any) {
console.log(error);
return new Response(`Internal server error`, {
status: 500,
});
}
}
My understanding is: Don't use a composite path like
'public/myFiles'
rather use: path.resolve('./public', 'myFiles')
Also, the following answer cleared my doubt about path.resolve
vs path.join
:
What's the difference between path.resolve and path.join?
I was using join
rather than resolve
in the first instance!