How-to: Upload direkt aus dem Frontend einer Vue App in ein AWS Bucket mit SignedURLs

Will man in seiner Vue App einen Upload für Nutzer bereitstellen, geht dies meist über das eigene Backend und ein damit verbundenes Daten-Handling. Mithilfe eines AWS Buckets und den SignedURLs ist dies relativ simpel zu erreichen, ohne große Last auf das eigene Backend zu legen.

Ich berichte von meinen Erfahrungen und Learnings bei der Umsetzung dieser Lösung. Vielleicht hilft es dem einen oder anderen Leser etwas schneller ans Ziel zu kommen.

Der Blogpost zeigt, mit welchem Setup der Upload zu AWS eingerichtet werden kann, und man komplett auf einen Backend-Server hierbei verzichten kann. Die Lernkurve bei AWS ist etwas steil, aber sobald das Grundsetup eingerichtet ist, lässt der Upload schnell realisieren.

Das brauchst du zum Starten

Setup deiner AWS Konsole

Schritt 1: Account anlegen

Im ersten Schritt erstellt man ein AWS Konto. AWS fragt bei einem neuen Account immer die Kreditkartendaten ab, obwohl wir später das Setup so einstellen, dass vorerst keine Kosten anfallen. Nach der erfolgreichen Anmeldung, haben wir Zugriff auf die zum Konto gehörenden Keys. Die angezeigten Schlüssel sind die Root-Schlüssel, die wir vorerst aber nicht nutzen werden, da einen IAM User für den Upload erstellen werden. Solltest du später diese Schlüssel benutzen wollen, kannst du dir einfach jederzeit neue Schlüssel unter “My Security Credentials” generieren lassen.

Beachte: Deine Schlüssel solltest du niemals öffentlich teilen, da es wegen des “Pay as you go” Preismodells schnell teuer für dich werden könnte. Auf dem sichersten Weg bist du, wenn du dir die Schlüssel als Variablen in einer .env-Datei in deinem Projekt hinterlegst und diese in deinem Git-Repository ignorierst

Schritt 2: Erstelle einen Bucket

Ist der Account angelegt, kann es mit der Bucket-Erstellung weitergehen. Ein Bucket lässt sich relativ einfach erstellen, indem du einen Name vergibst und alle Genehmigung vorerst deaktivierst. Dies ist wichtig, da sich erst danach die Bucket Policy auf public setzen lässt, um die Bilder für alle erreichbar zu machen.

Erstelle ein öffentliches Bucket (letzte Genehmigung auch ausschalten)

So sollten deine Bucket Policy und die CORS Einstellungen aussehen:

 {
    "Version": "2012-10-17",
    "Id": "public policy example",
    "Statement": [
        {
            "Sid": "Allow get requests",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::YOUR_BUCKET/*"
        }
    ]
}
<?xml version="1.0" encoding="UTF-8"?><CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><CORSRule>    <AllowedOrigin>*</AllowedOrigin>    <AllowedMethod>POST</AllowedMethod>    <AllowedMethod>GET</AllowedMethod>    <AllowedMethod>PUT</AllowedMethod>    <AllowedMethod>DELETE</AllowedMethod>    <AllowedMethod>HEAD</AllowedMethod>    
<AllowedHeader>*</AllowedHeader>
</CORSRule></CORSConfiguration>

Scritt 3: Erstelle einen neuen IAM Benutzer

Haben wir auch das Bucket angelegt, können wir uns nun der Erstellung eines IAM Benutzers widmen. Die richtig Seite für die Erstellung findest du am schnellsten über die Suche innerhalb der AWS Konsole.

Der neue Nutzer sollte nur Zugriff zum Get-, Put- und DeleteObject haben. Auch hier spielt die Sicherheit eine große Rolle. Sollten einmal Unbefugte Zugriff zu dem Schlüssel bekommen, haben diese dennoch keinen Vollzugriff auf das Bucket. Es empfiehlt sich deshalb eine neue Policy zu erstellen.

Hier ist ein Beispiel für eine Policy. Alle Schritte zum Erstellen eines neuen IAM Nutzers findest du in folgender Liste:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::YOUR_BUCKET/*"
        }
    ]
}
  • Navigiere zum User – der Link ist in der Sidebar, klicke auf Add user
  • Vergib einen Name eingeben und und wählen Programmatic access Dies erstellt einen eigenen Zugriffsschlüssel für den Nutzer, den wir später beim Upload nutzen.
  • Wähle “Attach existing policies directly” aus, um eine neue Policy unter “Create policy” erstellen zu können. Der Button wird einen neuen Tab öffnen.
  • Füge hier unter JSON einfach den Code von oben ein und klick “review Policy”. Notiz: Statt YOUR_BUCKET muss man natürlich den Name seines eigenen Buckets benutzen.
  • Vergib einen Namen für die Policy, und schließe den Tab mit “create policy”
  • Aktualisiere die Policies und wähle die neuerstellte Policy aus
  • Klicke “Next: Tags”, die du aber übergehen kannst, da wir Tags nicht brauchen
  • Nach der Erstellung des Users kannst du den Schlüssel einsehen. Man kann entweder den Text kopieren oder eine CSV Datei herunterladen. Diesen Schlüssel werden wir in der nächsten Schritt brauchen!

Das AWS JS SDK

Nach dem erfolgreichem Setup unserer AWS Konsole können wir endlich mit Coding anfangen. Ich habe die AWS Methoden aus Gründen der Wiederverwendbarkeit getrennt in einem aws.js File untergebracht.

Schritt 1: Erstellen einer S3 Instance

Zu allerst wollen wir die aws-sdk Bibliothek zu unserem Projekt hinzuzufügen, um die AWS Methode zu benutzenzu können. Zusätzlich für Requests nutzen wir auch das axios Package.

Installiere aws-sdk und axios in deinem Projekt yarn:

yarn add aws-sdk axios

Sobald die Installation erfolgreich war, können wir mit der Initialisierung einer S3-Instanz starten. Wie du siehst nutzen wir unsere Zugriffsschlüssel aus der .env-Datei sowie eine Regionsvariable. Mit new aws.S3() lässt sich eine S3-Instanz intialisieren. Wie du siehst habe ich auch die Option signatureVersion angeben, um Dateien zu dem Server hochladen zu können. Wenn du einen amerikanischen Server benutzt, kannst du dir diese Option sparen.

const aws = require('aws-sdk')

aws.config.update({
  secretAccessKey: process.env.VUE_APP_AWS_SECRET_ACCESS_KEY,
  accessKeyId: process.env.VUE_APP_AWS_ACCESS_KEY,
})

export const s3 = new aws.S3({
  signatureVersion: 'v4',
  region: process.env.VUE_APP_AWS_REGION,
})

Notiz: wenn du das Code kopierst, vergiss nicht in deinen .env Variablen VUE_APP_AWS_ACCESS_KEY, VUE_APP_AWS_SECRET_ACCESS_KEY und VUE_APP_AWS_REGION definieren.

Schritt 2: SignedURL

Wenn die Konfiguration abgeschlossen ist, können wir unsere singleUpload Methode mit signedURL anlegen. Ein kurzer Ausflug zum Thema signedURLs und warum es gut ist sie zu benutzen?

  • Die signedURL kann nur für einzelne Datei-Uploads benutzt werden – der Benutzer kann das Bucket nicht ungewollt weiter befüllen
  • Die signedURL verschlüsselt den Filename und Filetype – der Benutzer kann nur die jeweils angemeldete Datei hochladen
  • Die signedURL ist zeitlich beschränkt – dies schützt vor Exploits z.B.: Benutzer mit bösen Absichten, die versuchen, die signedURL von einem anderen Benutzer zu benutzen
  • Die signedURL ist von der Vue.js App generiert und lässt sich nicht selbst erstellen
  • Die signedURL funktioniert nur mit dem festgelegtem Bucket – Benutzer können kein andere Bucket sehen oder darauf zugreifen
export const singleUpload = (file, folder) => {
  const key = folder + '/' + Date.now() + '-' + file.name.replace(/\s/g, '-')
  const params = {
    Bucket: process.env.VUE_APP_AWS_BUCKET,
    Key: key,
    Expires: 10,
    ContentType: file.type,
  }
  const url = s3.getSignedUrl('putObject', params)
  return axios
    .put(url, file, {
      headers: {
        'Content-Type': file.type,
      },
    })
    .then(result => {
      const bucketUrl = decodeURIComponent(result.request.responseURL).split(
        key
      )[0]
      result.key = key
      result.fullPath = bucketUrl + key
      return result
    })
    .catch(err => {
      // TODO: error handling
      console.log(err)
    })
}

In Zeile 2 generieren wir einen spezifischen Dateinamen bei AWS als Key bezeichnet. Zusätzlich muss der Dateiname auch den Ordner enthalten in dem die Datei liegen soll, beispielsweise ein Album oder Team. Wir können den Ordnernamen mit Schrägstrich abgrenzen. Um einen einzigartigen Dateinamen zu generieren nutzen wir Date.now(). Die replace Methode ersetzt die Whitespaces gegen einen Bindestrich (-). Es wäre sogar möglich nur mit Date.now() zu arbeiten. Dies liegt bei dir, welche Struktur du in deinem Bucket aufbauen möchtest.

Wie ich oben schon erwähnt habe, beschränkt das “Expires” Attribut die URL zeitlich. Wenn du mehr über getSignedUrl erfahren willst, klicke auf dem Link.

Sobald die Datei hochgeladen ist, erhalten wir den Key und den Link zur Datei, diese geben wir zurück, um sie beispielsweise in unserer Datenbank mit abzulegen.

Schritt 3: Löschen die Datei

Das Löschen einer hochgeladenen Datei lässt sich ebenso einfach umsetzen. Man braucht nur das Bucket und den Name der Datei. Wenn man mehrere Buckets benutzt, dann speichert man lieber den Bucketnamen auch in der Datenbank mit ab. Beide Namen lassen sich dann aus der Datenbank ziehen. Nach dem erfolgreichen Löschen im Bucket, musst du natürlich die Datei auch aus deiner Datenbank löschen. 

export const deleteObjectByKey = key => {
  const params = {
    Bucket: process.env.VUE_APP_AWS_BUCKET,
    Key: key,
  }
  const data = s3.deleteObject(params).promise()

  return data
}

Upload Komponente in Vue mit Filepond

Wenn du deinen File-Upload nicht selbst stylen möchtest ist filepond sehr zu empfehlen. Mit der Bibliothek kannst in wenigen Minuten ein professionelles UI für den Upload implementieren.


Schritt 1: FilePond Komponente

Um die Bibliotheken nutzen zu können, fügen wir sie mit yarn wieder zu den Projekt Dependencies hinzu.

yarn add vue-filepont filepond-plugin-file-validate-type filepond-plugin-image-preview filepond-plugin-image-crop filepond-plugin-image-transform

Nach erfolgreichem Hinzufügen, kannst du vue-filepond in der gewünschten Vue-Komponente importieren.

import vueFilePond from 'vue-filepond'
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
import FilePondPluginImageCrop from 'filepond-plugin-image-crop'
import FilePondPluginImageTransform from 'filepond-plugin-image-transform'
  <FilePond
    ref="pond"
      :server="{
      process: (fieldName, file, metadata, load, error, progress, abort) => {
        uploadFile(file, metadata, load, error, progress, abort)
      },
    }"
    @removefile="onRemoveFile"
  />

Nun zur FilePond Komponente: Ref wird benötigt, um die Methode wie processFiles, addFile usw. mit den Komponente zu verbinden. Wenn die Datei bearbeitet wird, dann wird unsere uploadImages Methode mit den Parametern ausgeführt. Wichtig, die AWS Methoden müssen ebenso aus der aws.js importiert werden.

import { singleUpload, deleteObjectByKey } from '@/aws.js'

Schritt 2: So geht’s mit dem File-Upload

Der File-Upload in unserer Vue-App lässt sich nun recht einfach umsetzen. Wir rufen unsere uploadFile Methode mit der Datei und dem gewünschten Ordner als Parameter auf. Wenn der Upload erfolgreich war, erhalten wir eine Antwort mit dem Status 200.

async uploadFile(file, metadata, load, error, progress, abort){
      const result = await singleUpload(
        file,
        this.$route.params.teamSlug // folder of the file, you should change it to your variable or a string
      )
      if (result.status === 200) {
        // Handle storing it to your database here
        load(file) // Let FilePond know the processing is done
      } else {
        error() // Let FilePond know the upload was unsuccessful
      }
      return {
        abort: () => {
          // This function is entered if the user has tapped the cancel button
          // Let FilePond know the request has been cancelled
          abort()
        },
      }
},

Schritt 3: Rendern der Bilder

Um Dateien in seiner Vue-App später anzuzeigen, müssen spezifischen Datei-Daten wie Key und Url in der Datenbank gespeichert werden. Wenn man nur Bilder speichert, dann reicht auch nur der Key, da die URL in einem Computed Objekt generiert werden kann.

computed: {
  imgSrcArray: () => {
    return this.keys.map(url => 'https://s3.eu-central-1.amazonaws.com/vue-fileupload-example/' + url)
  },
},

Wichtig: Tausche eu-central-1 gegen deine Bucket-region und vue-fileupload-example gegen euren Bucket-name! Dann kannst du mir v-for beispielsweise eine Liste von Bilder rendern.

<img v-for="src in imgSrcArray" :src="src"/>

Schritt 4: Entfernung von Dateien

Im Schritt 1 habt ihr schon wahrscheinlich den v-on remove bemerkt. Jetzt zeige ich euch die Methode, die beim Löschen ausgeführt werden wird.

async onRemoveFile(event) {
      let url = this.$route.params.teamSlug + '/' + event.file.name // event.file.name has only the name after the slash / so the actual filename
      const res = await deleteObjectByKey(url)
      if (
        res.$response.httpResponse.statusCode >= 200 &&
        res.$response.httpResponse.statusCode < 300
      ){
        // here remove data from database too
      }
    },

Der StatusCode der Antwort zwischen 200 und 300 heißt, dass die Datei entweder gelöscht worden ist oder diese gar nicht existiert.

Resumé

Mithilfe eines AWS Buckets und der signedURL Funktion ist es relativ einfach einen Datei-Upload ohne große Einbindung des Backends zu realisieren. So wird das Backend nicht unnötig unter Last gesetzt. In Verbindung mit Vue und Filepond ist der gewünschte Upload über das Frontend einsatzbereit.

Was wir auf der VueJS Konferenz in London gelernt haben

Mit dem Ziel, die Vue.js Community besser kennenzulernen und Neuerung rund um das hippe JavaScript-Framework zu erfahren, haben wir uns auf den Weg nach London zur VueJS London gemacht. Für mich war es besonders außergewöhnlich, da ich früher weder geflogen bin noch in London war. London ist nicht nur wegen der Konferenz eine Reise wert. Die Stadt ist wirklich beeindruckend. Neuankömmlinge wie mich erkennt man relativ schnell, da sie beim Überqueren der Straße immer in die falsche Richtung schauen… *emoji*. 

Lufthansa brachte uns bequem nach London

Meetup der VueJS London Gruppe als Vorabevent

Als kleine Besonderheit vor der Konferenz hatte die lokale Vue.js Meetupgruppe ein Vorabevent organisiert. Bei spannenden Vorträgen von Chris Fritz (Mitglied des Vue Core Teams) und Jen Looper (Gründerin der Vue Vixen & senior Entwickler) und Chris DeMars zum Thema “accessibilty (A11Y)” gab es einen ersten Vorgeschmack auf die einen Tag später startende Konferenz. 

Der Vortrag von Chris, den Hauptverantwortlichen für die wirklich gut organisierte Vue.js Dokumentation, gab einen Überblick über neue Technologien und einige Tipps zum ‘best practices’. Die Neuerung bei den Vue Devtools unterstützen Entwickler immer besser dabei ihre Vue-Apps zu debuggen. Zudem hat er Weiterentwicklungen für die Vue CLI 3.0 damit verbundene Vue UI vorgestellt .

Jen Looper stellte vue-nativescript vor.

Jen Looper gab einen kleinen Talk zu den Grundlagen der mobilen Entwicklung mit Vue. In Verbindung mit NativeScript lassen sich in vertrauter Umgebung mobile Apps entwickeln. Jen hat ihre Eigenentwicklung vorgestellt: eine App die Schülern beim Sprachenlernen hilft. Die App bewertet die Genauigkeit der ausgesprochenen Wörter, sodass Aussprachefehler korrigiert werden können. 

Zweiter Tag – Tag der Workshops

Der offiziell erste Tag der Konferenz startet mit Tagesworkshops. Im Advanced Vue Workshop bei Chris Fritz gab es Tipps zum Vorgehen bei der Entwicklung größerer Applikationen. Der Workshop hat sich definitiv gelohnt. Wir haben die empfohlenen Grundsetups beleuchtet und mögliche Fallstricke besprochen, z. B.: den Unterschied zwischen scoped und module CSS, einige Wörter über ESlint und andere lints (z.B.: style). Chris hat auch sein entwickeltes Boilerplate für Enterprise Vue Apps vorgestellt.

Konferenztag mit Vorträgen

Der dritte Konferenztag war ein Feuerwerk an Vorträgen zum Framework. Auch war es irgendwie der Tag der Releases. Unter anderen wurde Nuxt 2.0 live auf der Bühne released.

Skype-Call mit Evan You

Der Kernentwickler Evan You ging in seinem Vortrag auf die Vorhaben bezüglich der Version 3 ein. Unter anderen soll die neue Version komplett in TypeScript entwickelt. Seine Ideen hat er auch nach dem Konferenz in einem Blogpost veröffentlicht. 

Jen Looper sprach über AI und ihre verschiedene Arten und wie sie lernen. Sie hatte eine mobile Beispiel-App mit NativeScript für Vue.js entwickelt Auch hier wurde auf der Bühne die Version 2 für nativescript-vue angekündigt..

Ein ziemlich beeindruckender Vortrag war die Präsentation der Neuerung der Codesandbox von Ives van Hoorne. Es ist nun möglich auch serverseitige Template innerhalb einer Sandbox zu nutzen. Somit können auch ziemlich schnell prototypische Nuxt.js Apps aufgesetzt werden.

Daniel Rossenwasser hat die Neuerung rund um die Arbeit mit TypesScript und Vue vorgestellt. Der Hinweis darauf, dass Vue 3 in TypeScript entwickelt wird, zeigt welche Richtung das Kernteam einschlägt.

VueJS und London waren definitiv eine Reise wert

Diese Reise hat sich auf jeden Fall gelohnt. Trotz zahlreicher Möglichkeiten sich im Web über VueJS auf dem Laufenden zu halten, war die VueJS London ein inspirierendes Erlebnis.

Alle Neuerung und Entwicklungen greifen wir auch in unserem Vue.js Workshop auf.

WordPress in weniger als einer Minute lokal installieren

WordPress lokal zu hosten und zu entwickeln, ist immer mit etwas Aufwand verbunden. Zunächst wird ein lokaler Server sowie eine MySQL-Datenbank benötigt, um WordPress überhaupt installieren zu können. Lange Zeit waren dafür Tools wie MAMP für den Mac die guten Begleiter eines WordPress-Entwicklers. Da Tools und Workflow mit der Zeit gehen sollten, gibt es hier aber starke Veränderungen. Wir stellen unseren aktuellen WordPress Workflow und die Tools, die wir dabei verwenden vor.

Grundsetup für unseren Workflow

Einrichtung der Tools

Moderne Workflows trennen die Entwicklungsumgebung von der Liveumgebung. Da bei der Entwicklung immer wieder Fehler auftreten können, muss abgesichert sein, dass die eigentliche Live-Seite davon nicht betroffen ist. Unser Workflow hat grundlegend immer folgende drei WordPress-Versionen:

  1. Lokal auf dem Rechner des Entwicklers 
  2. Test auf dem Staging Server getrennt von der Live Version
  3. Live-Version

Zuerst werden Features oder Anpassungen lokal auf dem Rechner des jeweiligen Entwicklers umgesetzt. Kann ein abgeschlossener Arbeitsschritt getestet werden, wird das Update auf den Testserver hochladen. Dieser beinhaltet eine von der Liveversion komplett getrennte WordPress-Installation und ist zum Beispiel auf dem Domain test.deindomain.de erreichbar. Erst wenn das Feature dort fehlerfrei getestet werden konnte, updaten wir die Live-Versionen mit den neuen Funktionen

Einrichtung der Tools

Sobald du dir Local von FlyWheel heruntergeladen hast, kann es mit der lokalen Einrichten eigentlich losgehen. Dazu startest du im ersten Schritt erst einmal das Programm.

Um eine neue lokale WordPress-Seite zu erstellen, starte man mit Create New Site den Wizard von Local. Es wird nun der Name der neuen WordPress-Seite abgefragt. Wenn du zusätzliche Optionen auswählen möchtest, wie zum Beispiel ein hinterlegtes Blueprint kannst du das unter den Advanced Options tuen. Ebenso kann hier der lokale Domain angepasst und das Ordnerverzeichnis ausgewählt werden. Im nächsten Schritt musst du die Serverkonfiguration auswählen. Wenn du keinerlei Besonderheiten hier benötigst, kannst du einfach das voreingestellte preferred aktiv lassen und weiterklicken.

Im letzten Schritt werden noch die WP-Admin Daten abgefragt, also jene Daten mit denen du dich nach der Installation in dein WordPress einloggen willst.

Erstelle in weniger als 30 Sekunden deine lokale WordPress Installation

Dann richtet Local die lokale Maschine ein und du hast nach kurzer Wartezeit deine WordPress-Seite erstellt. Die eigentliche Entwicklung kann jetzt endlich losgehen! 😉

Kurz noch ein Blick auf das Local Interface

Wenn Local deine Seite erstellt hat, erscheint die Seite neu in der linken Seitenleiste. Wählst du sie auch kannst du verschiedene Daten zu deiner lokalen Maschine sehen. Zum einen kannst du sie starten oder stoppen. Die Maschine muss natürlich gestartet sein, wenn du sie im Browser aufrufen willst. Den Link dazu findest du unter View Site und mit einen Klick auf Admin kommst du direkt zu WP Adminbereich. Neben Daten wie WordPress-Version, PHP-Version oder den Seitenpfad findest du am unteren Rand noch eine weitere Funktionen, die unser kleines Highlight ist. Du kannst einen Live-Link zu deiner lokalen Maschine erstellen.

Erstelle einen Live Link

Mit diesem Link kann die WordPress-Seite auch von extern aufgerufen werden, und es kann zügig Feedback zum aktuellen Entwicklungsstand eingeholt werden, ohne die Seite erst deployen zu müssen.

Vorhandene WordPress-Installationen synchronisieren

Wir haben nun eine lokale WordPress-Seite aufgesetzt und können diese nach Belieben starten oder stoppen. In den folgenden Beiträgen gehe ich darauf ein, wie es gelingt die lokale WordPress-Installation mit einer bestehenden Live-Versionen zu synchronisieren und wie man den Entwicklungs- und Deployment-Workflow mit Git absichert.

  • Part 2: Inhalte einer Live-Version in die lokale WordPress-Instanz importieren
  • Part 3: Deployment des WordPress-Themes mithilfe von Git und DeployHQ