title: How to execute server side file handling operations in Meteor?
body: <p>I'm storing Word(.docx) files using <a href="https://docs.mongodb.com/manual/core/gridfs/" rel="nofollow noreferrer">GridFS</a> on the server. I'd like to be able to merge the documents into one Word file by using the <a href="https://github.com/raulbojalil/docx-builder" rel="nofollow noreferrer">docx-builder</a> NPM package.</p>
  
  <p>Here's how I am uploading the files:</p>
  
  <pre><code>Meteor.methods({
      uploadFiles: function (files) {
        check(files, [Object]);
  
        if (files.length &lt; 1)
          throw new Meteor.Error("invalid-files", "No files were uploaded");
  
        var documentPaths = [];
  
        _.each(files, function (file) {
          ActivityFiles.insert(file, function (error, fileObj) {
            if (error) {
              console.log("Could not upload file");
            } else {
              documentPaths.push("/cfs/files/activities/" + fileObj._id);
            }
          });
        });
  
        return documentPaths;
      }
  })
  </code></pre>
  
  <p>How can I go about doing this on the server side ? I can only do this server side because the package I'm using requires the <code>fs</code> package which cannot be executed client side.</p>
  
  <p>Here's how I am trying to work on this at the moment. From the client, I'm calling the following method (which is declared as a <code>Meteor.method</code>):</p>
  
  <pre><code>print: function(programId) {
    // Get the program by ID.
    var program = Programs.findOne(programId);
    // Create a new document.
    var docx = new docxbuilder.Document();
    // Go through all activities in the program.
    program.activityIds.forEach(function(activityId) {
      // Create a temporary server side folder to store activity files.
      const tempDir = fs.mkdtempSync('/tmp/');
      // Get the activity by ID.
      var activity = Activities.findOne(activityId);
      // Get the document by ID.
      var document = ActivityFiles.findOne(activity.documents.pop()._id);
      // Declare file path to where file will be read.
      const filePath = tempDir + sep + document.name();
      // Create stream to write to path.
      const fileStream = fs.createWriteStream(filePath);
      // Read from document, write to file.
      document.createReadStream().pipe(fileStream);
      // Insert into final document when finished writinf to file.
      fileStream.on('finish', () =&gt; {
        docx.insertDocxSync(filePath);
        // Delete file when operation is completed.
        fs.unlinkSync(filePath);
      });
    });
    // Save the merged document.
    docx.save('/tmp' + sep + 'output.docx', function (error) {
      if (error) {
        console.log(error);
      }
      // Insert into Collection so client can access merged document.
      Fiber = Npm.require('fibers');
      Fiber(function() {
        ProgramFiles.insert('/tmp' + sep + 'output.docx');
      }).run();
    });
  }
  </code></pre>
  
  <p>However, when I'm downloading the final document from the <code>ProgramFiles</code> collection on the client side, the document is an empty Word document.</p>
  
  <p>What's going wrong here ?</p>
  
  <hr>
  
  <p><em>I've incorporated @FrederickStark's answer into my code. Just stuck on this part now.</em></p>
  
  <hr>
  
  <p>Here's another try:</p>
  
  <pre><code>'click .merge-icon': (e) =&gt; {
      var programId = Router.current().url.split('/').pop();
      var programObj = Programs.findOne(programId);
      var insertedDocuments = [];
      programObj.activityIds.forEach(function(activityId) {
        var activityObj = Activities.findOne(activityId);
        var documentObj = ActivityFiles.findOne(activityObj.documents.pop()._id);
        JSZipUtils.getBinaryContent(documentObj.url(), callback);
        function callback(error, content) {
          var zip = new JSZip(content);
          var doc = new Docxtemplater().loadZip(zip);
          var xml = zip.files[doc.fileTypeConfig.textPath].asText();
          xml = xml.substring(xml.indexOf("&lt;w:body&gt;") + 8);
          xml = xml.substring(0, xml.indexOf("&lt;/w:body&gt;"));
          xml = xml.substring(0, xml.indexOf("&lt;w:sectPr"));
          insertedDocuments.push(xml);
        }
      });
      JSZipUtils.getBinaryContent('/assets/template.docx', callback);
      function callback(error, content) {
        var zip = new JSZip(content);
        var doc = new Docxtemplater().loadZip(zip);
        console.log(doc);
        setData(doc);
      }
  
  
      function setData(doc) {
        doc.setData({
          // Insert blank line between contents.
          inserted_docs_formatted: insertedDocuments.join('&lt;w:br/&gt;&lt;w:br/&gt;')
          // The template file must use a `{@inserted_docs_formatted}` placeholder
          // that will be replaced by the above value.
        });
  
        doc.render();
  
        useResult(doc);
      }
  
      function useResult(doc) {
        var out = doc.getZip().generate({
          type: 'blob',
          mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        });
        saveAs(out, 'output.docx');
      }
  </code></pre>