VueJS 3, ExpressJS 4.17 | Upload et redimensionnement d’une image

Upload d’une image et redimensionnement avec VueJS 3 et ExpressJS 4.17

Je vais vous présenter comment faire un formulaire simple avec VueJS 3, pour pouvoir upload une image ainsi qu’un champ de texte qui pourrait servir au texte alternatif de l’image.

Le serveur tourne sous ExpressJS 4.17.

Nous allons avoir besoin de plusieurs librairies :

Front VueJS

  • Axios (V0.21) pour effectuer des requêtes HTTP vers le serveur

Back ExpressJS

  • Multer (V 1.4.2) pour récupérer l’image du formulaire et l’insérer dans le dossier
  • Sharp (V 0.26.3) pour redimensionner l’image et la mettre dans un dossier de miniature
  • Cors (V 2.8.5) dans le cas d’un probleme de Cross origin

Une fois les dépendances installées, commençons.

Formulaire VueJS

Il nous faut seulement un input file et un input de texte

<template>
  <div id='main'>
      <input type="file" name="picture" id="picture">
      <!-- Input text lié à une variable de vuejs pour le texte alternatif de l'image -->
      <input type="text" name="alt-picture" id="" placeholder="alt text" v-model="alt_text">
      <!-- Le bouton d'envoi lié à une fonction d'envoi -->
      <button @click="envoi()">Envoyer</button>
  </div>
</template>

Nous donnons un id au input file pour récupérer sa photo plus tard, et nous lions l’input texte à une variable pour un refresh en temps réel

Il nous faut également notre fonction pour envoyer le formulaire au serveur grâce à Axios !

Voici le script du component VueJS :

<script>
import axios from 'axios'

export default {
  name: 'App',
   data() {
    return {
      alt_text: ''
    }
  },
  methods: {
    envoi(){
      // Récupération de l'image
      let img = document.getElementById('picture').files[0]
      // Création d'un formData obligatoire pour envoi de l'image
        var formData = new FormData()
        formData.append('img', img)
        formData.append('alt_text', this.alt_text)
        // Envoi des données sur l'url du serveur (mettez la votre) en POST en envoyant le formData contenant notre image et notre texte
        axios.post('http://localhost:3000/upload_image', formData)
          .then((resp) => {
            console.log(resp)
          })
          .catch((err) => {
            console.log(err.response)
          })
    }
  }
}
</script>

Nous avons donc besoin d’importer Axios, de créer notre méthode qui sera appelée par le bouton d’envoi, et dedans récupérer notre image, la mettre dans un formData avec notre texte alternatif et d’envoyer tout cela au serveur ExpressJS via Axios.

Côté serveur ExpressJS

Tout d’abord pour notre serveur il est nécessaire d’importer nos dépendances et de mettre en place le Cross origin 

//Import de nos dépendances
const express = require('express')
const multer = require('multer')
const sharp = require('sharp')
const path = require('path')
var cors = require('cors')

const app = express()

app.use(express.json())

// Mise en place du Cross origin pour éviter les erreurs dans vuejs
app.use(cors())
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*')
  // authorized headers for preflight requests
  // https://developer.mozilla.org/en-US/docs/Glossary/preflight_request
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept',
  )
  next()

  app.options('*', (req, res) => {
    // allowed XHR methods
    res.header(
      'Access-Control-Allow-Methods',
      'GET, PATCH, PUT, POST, DELETE, OPTIONS',
    )
    res.send()
  })
})

Ensuite nous allons attaquer la partie de configuration de Multer pour que la librairie sache où l’on souhaite uploader l’image ainsi que d’autres paramètres.

Après le Cross origin mettez en place la configuration Multer :

// Création du diskStorage de multer, il permet de définir notre configuration d'upload
// /!\ Créez les dossiers de destination au cas où avant l'upload
var storage = multer.diskStorage({
  // La limite en taille du fichier
  limits: {
    fileSize: 1000000, //1Mo
  },
  // La destination, ici ce sera à la racine dans le dossier img
  destination: function (req, file, cb) {
    cb(null, './img')
  },
  // Gestion des erreurs
  fileFilter(req, file, cb) {
    if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) {
      return cb(new Error('Le fichier doit etre un JPG'))
    }
    cb(undefined, true)
  },
  // Fonction qui renomme l'image
  filename: function (req, file, cb) {
    // Genère un nom aléatoire et récupère l'ancienne extension
    cb(
      null,
      Math.random().toString(36).substring(7) +
        '.' +
        file.originalname.split('.')[1],
    )
  },
})

// Création de l'objet multer
const upload = multer({
  storage: storage,
})

Vous pouvez lire dans les commentaires à quoi sert chaque choses, globalement Multer nous permet de choisir un dossier de destination, appliquer des règles de limites et d’extension de fichier, ainsi que de pouvoir renommer l’image.

Route POST de réception d’image

Il est maintenant nécessaire de mettre en place notre route qui va réceptionner nos superbes images, ainsi que de lancer notre serveur !

// Utilisation de l'objet multer en tant que middleware
// Cela veut dire qu'en premier multer va upload notre image, et qu'ensuite nous passons à la fonction de notre route /upload_image
app.post('/upload_image', upload.single('img'), async (req, res) => {
  try {
    if (req.file) {
      // Utilise la librairie sharp pour redimensionner en 200x100, et renvoi la miniature dans un autre fichier dans le dossier de destination choisi dans le diskStorage
      await sharp(req.file.path, { failOnError: false })
        .resize({ width: 200, height: 100 })
        .toFile(
          path.resolve(req.file.destination + '/thumbnail', req.file.filename),
        )
      // Vous pouvez utiliser ces variables pour faire des insertions en base de données ou autre
      let filename = req.file.filename
      let alt_text = req.body.alt_text
    }
    res.send('Upload fini')
  } catch (e) {
    res.status(400).send(e)
  }
})

app.listen(3000, () => {
  console.log('Serveur lancé sur le port : ' + 3000)
})

Nous avons créer notre route, mis en place le middleware  qui nous permet d’upload l’image, et nous avons ensuite défini la fonction de la route comme asynchrone pour le redimensionnement de notre image.

Après l’upload de l’image principale, Sharp va se charger de la prendre et de la retailler, dans l’exemple j’ai mis 200×100 mais il est possible de mettre n’importe quelle valeur.

Suite au redimensionnement Sharp mets notre fichier dans le dossier de destination de notre image principale dans un dossier de miniature, grâce à la méthode .toFile().

Ici aussi vous pouvez choisir n’importe quelle destination.

Ensuite nous pouvons avoir accès au texte alternatif ainsi que le nom de l’image si nous souhaitons stocker ces données en base de données pour l’afficher sous VueJS plus tard !

Nous gagnons environ 97% de place de stockage grâce au redimensionnement.

Ce tutoriel est maintenant terminé, merci d’avoir lu et n’hésitez pas à partager 🙂