javascriptnode.jsexpresspugmulter

How to post both text and image to backend with Node.JS PUG


I have the following PUG template:

extends layout
block append head
  link(type='stylesheet', href='/stylesheets/mystyle.css', rel='stylesheet')
block layout-content
  #chat-app
    #chat.has-text-centered
      h1.title Welcome to #{title}
      section.section.chat-container
        .container
          .columns
            .box.column.is-full
              h2.title Messages
              .chat-messages.has-text-left
                p#messages
              form#chatForm(action='/api/gemini', method='post', enctype="multipart/form-data")
                .field.has-addons
                  p.control.is-expanded
                    input(class='input', id='prompt', type='text', placeholder='How can I help?')
                  p.control
                    input(class='input', id='image', type='file')
                  p.control
                    input(class='button is-success', type='submit', value='Post')                
      script.
        document.querySelector("#chatForm").addEventListener("submit", async (e) => {
          e.preventDefault();
          const prompt = document.querySelector("#prompt").value;
          if (prompt.trim()) {
            const response = await fetch('/api/myapi', {
              method: 'POST',
              headers: { 'Content-Type': 'multipart/form-data' },
              body: JSON.stringify({, prompt:, prompt})
            });
            const data = await response.json();
            document.querySelector("#messages").innerHTML += `<p><strong>You:</strong> ${prompt}</p>`;
            document.querySelector("#messages").innerHTML += `<p><strong>Server:</strong> ${data.message}</p><br>`;
            document.querySelector("#prompt").value = '';
          }
        });

And the server endpoint using multer:

api.post('/myapi', upload.single('image'), function (req, res, next) { myapp.Handle(req, res, next); });

I can use either multer or express-fileupload. The concern here is the inline script in the PUG template. How can I send BOTH the text message and upload file in the same post request?


Solution

  • Use FormData to construct multipart request: append file to image key, and then append other data (JSON) to other key, and simply post form data as body, without content type headers, because it will automatically construct multipart request (file will be in req.file, just make sure that file field name is the same on the server (.append('image'... -> upload.single('image'), and other data in req.body in key/value form):

    // get the image
    const image = document.querySelector("#image").files[0];
    
    const formData = new FormData();
    
    // add the image -> access via `req.file`,
    // also, field name must be the same on the server (image -> image)
    formData.append('image', image);
    
    // add other stuff -> acess via `req.body`
    formData.append('prompt', JSON.stringify({prompt}));
    
    // send data - it automatically constructs multipart request
    // no need to manually add content type headers
    const response = await fetch('/api/myapi', {
      method: 'POST',
      body: formData
    });
    

    or even shorter: you could simply pass the form element to FormData, and it would be done automatically (all HTML form fields will be sent in key/value form):

    note: for key/value pairs to work, you need to add corresponding name attributes to the form's input elements

    input(class='input', name='prompt', id='prompt', type='text', placeholder='How can I help?')
    
    input(class='input', name='image', id='image', type='file')
    

    etc.

    const response = await fetch('/api/myapi', {
      method: 'POST',
      body: new FormData(e.target)
    });