Well great… I’m a MASSIVE Next.js fan… I’ve been using it exclusively for all my recent projects. Since you’re self-hosted… what if you were to leverage a filesystem store for your pdf’s…
{
"_id": ObjectId("..."),
"title": "title of your PDF",
"filePath": "/uploads/your.pdf",
"mimeType": "application/pdf",
"uploadedAt": ISODate("2025-04-01T10:00:00Z")
}
I’m using Multer for file uploads.
I’d build a project structure something like this:
your_app/
├── pages/
│ └── api/
│ ├── upload.js
│ └── file/[id].js
├── uploads/
├── lib/
│ └── mongodb.js
Basic MongoDB Connection:
import { MongoClient } from 'mongodb';
const uri = process.env.MONGODB_URI;
const client = new MongoClient(uri);
export async function connectToDatabase() {
if (!client.isConnected()) await client.connect();
const db = client.db(); // or specify your specific db name
return { db, client };
}
Then create an api upload:
import nextConnect from 'next-connect';
import multer from 'multer';
import { connectToDatabase } from '../../lib/mongodb';
import path from 'path';
import fs from 'fs';
const uploadDir = './uploads';
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
const upload = multer({ dest: uploadDir });
const apiRoute = nextConnect({
onError(error, req, res) {
res.status(501).json({ error: `Upload failed: ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method ${req.method} not allowed` });
},
});
apiRoute.use(upload.single('file'));
apiRoute.post(async (req, res) => {
const { db } = await connectToDatabase();
const { originalname, filename, mimetype, path: filepath } = req.file;
const result = await db.collection('files').insertOne({
originalname,
filename,
mimetype,
filepath,
uploadedAt: new Date()
});
res.status(200).json({ success: true, id: result.insertedId });
});
export default apiRoute;
export const config = { api: { bodyParser: false } };
and maybe a file server:
import { connectToDatabase } from '../../../lib/mongodb';
import { ObjectId } from 'mongodb';
import fs from 'fs';
import path from 'path';
export default async function handler(req, res) {
const {
query: { id },
} = req;
const { db } = await connectToDatabase();
const fileDoc = await db.collection('files').findOne({ _id: new ObjectId(id) });
if (!fileDoc) {
res.status(404).json({ error: 'File not found' });
return;
}
const filePath = path.join(process.cwd(), fileDoc.filepath);
const fileStream = fs.createReadStream(filePath);
res.setHeader('Content-Type', fileDoc.mimetype);
fileStream.pipe(res);
}
You could use a simple form in your UI:
const uploadFile = async (event) => {
const formData = new FormData();
formData.append("file", event.target.files[0]);
const res = await fetch("/api/upload", {
method: "POST",
body: formData,
});
const data = await res.json();
console.log("Uploaded File ID:", data.id);
};
Hope this helps! Let us know how you make out.
Regards,
Mike
https://mlynn.org