h3 icon indicating copy to clipboard operation
h3 copied to clipboard

Accept File upload!

Open folamy opened this issue 2 years ago • 14 comments

How do I use h3 to get files from client/browser for file upload? I am coming from ExpressJs, where I can install a package called express-fileupload. Link: Express Fileupload

const express = require('express');
const fileUpload = require('express-fileupload');
const app = express();
app.use(fileUpload())
Then 
app.post ('/someApi' , (req, res) =>{
   console.log(req.files)
})

How do I achieve this?

folamy avatar Oct 14 '21 15:10 folamy

Idk maybe multer or formidable would work

import * as formidable from 'formidable'
import { createApp } from 'h3'

const form = formidable({ multiples: true })

app.use('/upload', async (req, res) => {
    form.parse(req, (err, fields, files) => {
        // do something
    })
   
   // return
});

wobsoriano avatar Oct 18 '21 14:10 wobsoriano

Idk maybe multer or formidable would work

import * as formidable from 'formidable'
import { createApp } from 'h3'

const form = formidable({ multiples: true })

app.use('/upload', async (req, res) => {
    form.parse(req, (err, fields, files) => {
        // do something
    })
   
   // return
});

I am not sure of this would work either. I believe you are trying to create a new instance of h3 app that is different from nuxt3 server!

folamy avatar Oct 18 '21 15:10 folamy

Sorry, I forgot to mention that I am using nuxt 3!

folamy avatar Oct 18 '21 15:10 folamy

Then do it like this?

import type { IncomingMessage, ServerResponse } from 'http'

const form = formidable({ multiples: true })

export default async (req: IncomingMessage, res: ServerResponse) => {
    form.parse(req, (err, fields, files) => {
        // do something
    })

    // return
}

wobsoriano avatar Oct 18 '21 15:10 wobsoriano

Thanks, I will try that and let you know the outcome.

folamy avatar Oct 18 '21 15:10 folamy

Then do it like this?

import type { IncomingMessage, ServerResponse } from 'http'

const form = formidable({ multiples: true })

export default async (req: IncomingMessage, res: ServerResponse) => {
    form.parse(req, (err, fields, files) => {
        // do something
    })

    // return
}

@wobsoriano, do you know how I can integrate socketio with nuxt3? My major challenge is that I don't have access to the main server instance.

folamy avatar Oct 18 '21 15:10 folamy

Not sure about this but you can try

// server/middleware/socket.ts
import type { IncomingMessage, ServerResponse } from 'http'
import { Server } from 'socket.io'

let server: any = null

export default (req: IncomingMessage, res: ServerResponse) => {
  if (!server) {
    // @ts-expect-error: Nuxt3
    server = res.socket?.server
    const io = new Server(server);

    io.on('connection', (socket) => {
      console.log('Made socket connection');

      socket.on('msg', (msg) => {
        console.log('Recived: ' + msg)

        setTimeout(() => {
          socket.emit('msg', `Response to: ${msg}`)
        }, 1000)
      })

      socket.on('disconnect', () => console.log('disconnected'))
    })
  }
}

Edit: Tried this, works :)

https://gist.github.com/wobsoriano/79fa3be004f0a1b70f94e34dfbade897

wobsoriano avatar Oct 18 '21 16:10 wobsoriano

How are you guys sending the data from client? I've been trying to get a image file to transfer to the server side and I'm just not getting it there. I'm not sure what I'm missing

bfg-coding avatar Nov 29 '21 22:11 bfg-coding

How are you guys sending the data from client? I've been trying to get a image file to transfer to the server side and I'm just not getting it there. I'm not sure what I'm missing

Using https://developer.mozilla.org/en-US/docs/Web/API/FormData

wobsoriano avatar Nov 30 '21 03:11 wobsoriano

@wobsoriano Thank you, I found that earlier so it's good to hear I'm on the right track. Here is my current setup

Client

const fd = new FormData();
    fd.append("name", fileName);
    fd.append("image", file);

    axios.post("/api/avatars", fd, {
      headers: {
        'accept': 'application/json',
        'Accept-Language': 'en-US,en;q=0.8',
        'Content-Type': `multipart/form-data; boundary=${file._boundary}`,
      }
    }).then(resp => { console.log(resp)});

Nuxt3 server/api/avatars

import {IncomingMessage, ServerResponse} from "http";
import formidable from "formidable";

const form = new formidable.IncomingForm();

export default async (req: IncomingMessage, res: ServerResponse) => {
    form.parse(req, async (err, fields, files) => {
        console.log(err);
        console.log(fields);
        console.log(files);
    })
    res.end("Got data");
}

The error I'm getting now is

 ERROR  [proxy] write EPIPE                                                                                                                                                                                                                                                                                                                                                                                                                                    

  at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:98:16)

I'd appreciate your continue help as I've been stuck on this for a day and a half now.

Thank you

bfg-coding avatar Nov 30 '21 06:11 bfg-coding

The last comment was left back 4 months ago, but @bfg-coding this happened to me too today. What I noticed in the document from Fromdable was they wrapped the form parse function in the promises.

Without it, that's what I encountered

 ERROR  [proxy] write EPIPE                                                                                                                                                                                                                                                                                                                                                                                                                                    

  at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:98:16)

So the final code should be :


export default async (req, res) => {

  const form = formidable({ multiples: true });

    await new Promise((resolve, reject) => {
      form.parse(req, (err, fields, files) => {
        console.log(files]
      });
    });

    return {}

}

And I get this:

{
  image: [
    PersistentFile {
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      lastModifiedDate: 2022-03-16T14:56:34.284Z,
      filepath: '/var/folders/93/m7bwg2ds5gzf4w35bctsb1k40000gn/T/eff7ead095d57e3ed63a44900',
      newFilename: 'eff7ead095d57e3ed63a44900',
      originalFilename: '1.jpeg',
      mimetype: 'image/jpeg',
      hashAlgorithm: false,
      size: 317210,
      _writeStream: [WriteStream],
      hash: null,
      [Symbol(kCapture)]: false
    }
  ]
}

Hope this helps some newbies like me guide to the backend world of node frameworks.

thingsneverchange avatar Mar 16 '22 15:03 thingsneverchange

Partially related, but how do you guys serve the uploaded file using h3 handler?

I'm trying:

// server/api/file.ts

import type { ServerResponse } from "http";
import { CompatibilityEvent } from "h3";
import fs from "fs";

export default async (event: CompatibilityEvent, res: ServerResponse) => {
  const filename = "./uploads/file.jpg";

  const readStream = fs.createReadStream(filename);

  readStream.on("error", function (err) {
    console.log(`error: ${err.message}`);
    res.end(err.message);
  });

  readStream.on("open", function () {
    res.statusCode = 200;
    readStream.pipe(res);
  });
};

But it gives 404 error:

{
"url": "/api/file",
"statusCode": 404,
"statusMessage": "Not Found",
"message": "Not Found"
}

Also asked at Nuxt 3 https://github.com/nuxt/framework/discussions/4299 and found similar question at h3 https://github.com/unjs/h3/issues/93

zelid avatar Apr 12 '22 14:04 zelid

I am using this inside my /api/media/[id].get.ts:

import fs from 'fs'
import { sendStream } from 'h3'
import { getFilepathFromId } from '~/server/lib/fileStorage'

export default defineEventHandler(async (event) => {
  const fileId = event.context.params.id
  const filepath = getFilepathFromId(fileId)

  return sendStream(event, fs.createReadStream(filepath))
})

I assumes the fileId exists in every case :) It works perfectly except for Safari video streaming that I am currently debugging.

Lyokolux avatar Sep 19 '22 09:09 Lyokolux

Here's a tangential tutorial that is helpful: https://blog.replit.com/build-a-speech-to-text-app-with-assemblyai-on-replit

a-toms avatar Oct 05 '22 16:10 a-toms

For everyone refering to this issue, I actually had to remove the content-type header on the Client to make this work. Otherwise this happened: https://stackoverflow.com/questions/41138443/multipartparser-end-stream-ended-unexpectedly

So the updated Client example would be:

const fd = new FormData();
fd.append("name", fileName);
fd.append("image", file);

axios.post("/api/avatars", fd, {
  headers: {
    'accept': 'application/json',
    'Accept-Language': 'en-US,en;q=0.8',
  }
}).then(resp => { console.log(resp)});

madebyfabian avatar Dec 09 '22 14:12 madebyfabian