Hello everyone I am developing a website using React.js and Node.js where I generate a word document from the user's input and thats worked just fine with docxtemplater!
The problem is I want to let the user download the word document direclty as a PDF document but docxtemplater just permit us to save the file as a docx. To convert the docx, i have the idea to save my blob document in mongodb with gridfs (already done and worked fine) and then get my blob and convert it to pdf (where i'm blocked)
Here is the part of my code for generate my docx and saving the generated word docx in mongodb (i have intentionnaly delete some things not important for this question)
import React, { useState, useEffect } from "react";
import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import PizZipUtils from "pizzip/utils/index.js";
import { saveAs } from "file-saver";
import axios from "axios";
function loadFile(url, callback) {
PizZipUtils.getBinaryContent(url, callback);
}
export const DocxFile = ({ formData }) => {
const [file, setFile] = useState(null);
const [currentlyUploading, setCurrentlyUploading] = useState(false);
const [docxId, setDocxId] = useState(null);
const [progress, setProgress] = useState(null);
const [inputContainsFile, setInputContainsFile] = useState(false);
const [template, setTemplate] = useState();
const generateDocument = () => {
loadFile(template, function (error, content) {
if (error) {
throw error;
}
var zip = new PizZip(content);
var doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
doc.setData({
company: formData.general.companyClient,
version: formData.documentVersion,
quote: formData.general.quoteNumber,
HERE MY DATA //Working :)
});
try {
doc.render();
} catch (error) {
function replaceErrors(key, value) {
if (value instanceof Error) {
return Object.getOwnPropertyNames(value).reduce(function (
error,
key
) {
error[key] = value[key];
return error;
},
{});
}
return value;
}
if (error.properties && error.properties.errors instanceof Array) {
const errorMessages = error.properties.errors
.map(function (error) {
return error.properties.explanation;
})
.join("\n");
}
throw error;
}
var out = doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
saveAs(
out,
`${name}.${documentVersion}.docx`
);
setFile(out);
setInputContainsFile(true);
});
};
const fileUploadHandler = () => {
const fd = new FormData();
fd.append("docx", file, file.name);
axios
.post(`api/docx/upload`, fd, {
onUploadProgress: (ProgressEvent) => {
setProgress((ProgressEvent.loaded / ProgressEvent.total) * 100);
console.log(
"upload progress",
Math.round((ProgressEvent.loaded / ProgressEvent.total) * 100)
);
},
})
.then(({ data }) => {
setDocxId(data);
setFile(null);
setInputContainsFile(false);
setCurrentlyUploading(false);
})
.catch((err) => {
console.log(err);
if (err.response.status === 400) {
const errMsg = err.response.data;
if (errMsg) {
console.log(errMsg);
}
} else {
console.log("other error", err);
setInputContainsFile(false);
setCurrentlyUploading(false);
}
});
};
const handleClick = () => {
if (inputContainsFile) {
setCurrentlyUploading(true);
fileUploadHandler();
}
};
return (
<>
<button
className="modify__button1 enregistrer generator__button"
onClick={generateDocument}
>
Generate the docx
</button>
<label htmlFor="file" onClick={handleClick}>
{file ? <>SUBMIT</> : <></>}
</label>
</>
);
};
This is what i got on mongodb after sending : MongodbBLOB
After that, i will do a get request to get my blob ( i already know how to get my blob) But then, how can i convert it to pdf (and keep the template used for my docx) ?
The best option I found (without writing any stuff on the disc!) based on libreoffice-convert
npm (you still need libreoffice installed on your machine, but don't install the full GUI version of LibreOffice, use the lightweight headless version: apt install -y libreoffice-nogui
).
Here is the code:).
Now I can easily create both DOCX and PDF based on the same template of docxtemplater ;-)
My React code:
// In your controller or service:
export const exportTextToFile = async (text: string, filetype: string): Promise<Blob> => {
const exportToFileEndpointUrl = `${API_BASE_URL}/api/export/${filetype}`;
const response = await axios.post<ArrayBuffer>(exportToFileEndpointUrl, text, {
responseType: 'arraybuffer',
});
return new Blob([response.data]);
};
// In your component:
const getAndDownloadFile = async (filetype: string) => {
try {
const blob = await exportTextToFile(text, filetype);
saveAs(blob, `MyFile.${filetype}`);
} catch (error) {
console.error(error);
}
};
In NodeJS backend app:
import libre from 'libreoffice-convert';
try {
const docxBuffer = await generateDocx(...);
if (filetype === 'pdf') {
libre.convert(docxBuffer, 'pdf', undefined, (err, pdfBuffer) => {
if (err) {
throw err;
} else {
res.send(pdfBuffer);
}
});
} else if (filetype === 'docx') {
res.send(docxBuffer);
}
logger.info('Sent file buffer to the client', { filetype });
} catch (error) {
logger.error('Error during file creation', { filetype, error: error.message });
res.sendStatus(500);
}
// Your docxtemplater generator:
import Docxtemplater from 'docxtemplater';
function generateDocx(...): Promise<Buffer> {
const content = fs.readFileSync(path.resolve(__dirname, 'your-template.docx'), 'binary');
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
doc.render({
/// your template params
});
const buffer = doc.getZip().generate({
type: 'nodebuffer',
compression: 'DEFLATE',
});
return buffer;
}