Dateien mit Uppy, XHRUpload und Express hochladen
Wählen Sie Dateien mit dem Dashboard von Uppy aus und senden Sie sie mit XHRUpload an einen Multipart-Endpunkt. Diese Anleitung bietet ein vollständiges lokales Browser-Server-Beispiel: eine Dateiauswahl mit Fortschrittsanzeige und Steuerung für erneute Versuche sowie einen Express-Empfänger, der die Byte-Anzahl und den SHA-256-Hash jedes Uploads zurückgibt.
Legen Sie die Upload-Schnittstelle fest
Das Dashboard stellt die Oberfläche bereit; XHRUpload sendet
pro Datei einen Multipart-POST. Der Empfänger erwartet das Feld file
und antwortet mit JSON, nachdem er die gesamte Datei gelesen hat. Neben Uppy gibt es keinen
zweiten, manuell geschriebenen XMLHttpRequest.
Dies ist eine lokale Demo zur Dateiübertragung. Der Empfänger hält jede Datei während der Bearbeitung ihrer Anfrage im Arbeitsspeicher, berechnet ihren Hash und verwirft sie danach. Er speichert keine Dateien, gibt keine Download-Links zurück und untersucht ihre Inhalte nicht. Eine erfolgreiche Antwort bedeutet, dass der Empfänger die Bytes gelesen hat, nicht, dass er sie dauerhaft gespeichert hat.
Erstellen Sie das Projekt
Verwenden Sie Bash unter Linux, Node.js 24.15.0 und Yarn 4.12.0, verfügbar über
corepack yarn. Das Beispiel läuft auch mit Node.js 26.8.1; die Überprüfung im
Browser erfolgt mit Chromium 145. Node führt server.ts mit
integriertem TypeScript-Stripping aus.
Der Browser-Code wird mit esbuild verarbeitet.
Fügen Sie dies in einem Verzeichnis ein, in dem ein neuer Ordner namens
uppy-xhr-demo entstehen soll. Die Klammern sorgen dafür, dass Ihre Shell im
ursprünglichen Verzeichnis bleibt. Falls der Ordner bereits existiert, stoppt die Einrichtung,
ohne ihn zu verändern; wählen Sie einen neuen Speicherort. Falls die Installation fehlschlägt,
stoppen Sie und beheben Sie den Fehler, bevor Sie fortfahren.
(
mkdir uppy-xhr-demo &&
cd uppy-xhr-demo &&
printf '%s\n' '{"private":true,"type":"module","packageManager":"yarn@4.12.0"}' > package.json &&
printf '%s\n' 'nodeLinker: node-modules' > .yarnrc.yml &&
touch yarn.lock &&
corepack yarn add --exact @uppy/core@6.0.2 @uppy/dashboard@6.0.0 @uppy/xhr-upload@6.0.0 express@5.2.1 multer@2.4.0 esbuild@0.27.0 &&
mkdir public
)
Die leere Lockdatei macht daraus ein eigenständiges Yarn-Projekt, auch innerhalb eines anderen
Projekts. Bewahren Sie die erzeugte Datei yarn.lock auf, um die getestete
Kombination reproduzierbar zu installieren. Die lokale Datei package.json
legt ES-Module fest, selbst innerhalb eines übergeordneten CommonJS-Projekts;
der Yarn-Linker node-modules ermöglicht es
Node, die Importe des Servers ohne zusätzliche Hilfsmittel aufzulösen.
Speichern Sie die folgenden drei Dateien in uppy-xhr-demo.
Fügen Sie den Empfänger hinzu
Speichern Sie dies als server.ts.
Multer parst den Multipart-Body und setzt bereits beim Empfang
Anfragelimits durch. Der Empfänger akzeptiert eine Datei pro Anfrage, bis zu 2 MiB, ohne
zusätzliche Textfelder. Die MIME-Prüfung prüft nur die Angabe des Senders; sie belegt nicht, dass
die Bytes ein gültiges Bild oder PDF bilden.
import { createHash } from 'node:crypto'
import { join } from 'node:path'
import express from 'express'
import multer from 'multer'
const app = express()
const receive = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 2 * 1024 * 1024, files: 1, fields: 0 },
}).single('file')
const allowedTypes = new Set(['image/jpeg', 'image/png', 'application/pdf'])
app.use(express.static(join(import.meta.dirname, 'public')))
app.post('/upload', (req, res) => {
receive(req, res, (error: unknown) => {
if (error) {
const tooLarge = error instanceof multer.MulterError && error.code === 'LIMIT_FILE_SIZE'
res.status(tooLarge ? 413 : 400).json({ error: 'Upload rejected.' })
return
}
const file = req.file
if (!file) {
res.status(400).json({ error: 'Expected one file in the file field.' })
return
}
if (!allowedTypes.has(file.mimetype)) {
res.status(415).json({ error: 'Expected a JPEG, PNG, or PDF MIME type.' })
return
}
res.json({
bytes: file.size,
sha256: createHash('sha256').update(file.buffer).digest('hex'),
})
})
})
const port = Number(process.env.PORT ?? 0)
if (!Number.isInteger(port) || port < 0 || port > 65535) {
throw new Error('PORT must be an integer from 0 to 65535.')
}
const server = app.listen(port, '127.0.0.1', (error?: Error) => {
if (error) {
console.error(`Cannot start the upload server: ${error.message}`)
process.exitCode = 1
return
}
const address = server.address()
if (!address || typeof address === 'string') throw new Error('Missing listening address.')
console.log(`Open http://127.0.0.1:${address.port}`)
})
Port null fordert vom Betriebssystem einen verfügbaren Port an. Bei Bedarf können Sie
PORT auf einen festen Port setzen. Der Callback behandelt
Startfehler von Express 5, sodass bei einem belegten Port der
Prozess mit einem Fehler endet, statt irreführend eine einsatzbereite Adresse auszugeben.
Fügen Sie die Seite und den Dateiuploader hinzu
Speichern Sie dies als public/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Uppy upload demo</title>
<link rel="icon" href="data:," />
<link rel="stylesheet" href="/app.css" />
<style>
body { margin: 1rem; font-family: sans-serif; }
#receipts { overflow-wrap: anywhere; }
</style>
<script type="module" src="/app.js"></script>
</head>
<body>
<h1>Upload to the local receiver</h1>
<div id="drag-drop-area"></div>
<h2>Received by the server</h2>
<p>These receipts confirm transfer. Files are not saved.</p>
<ul id="receipts" aria-label="Server receipts" aria-live="polite"></ul>
</body>
</html>
Speichern Sie dies als client.ts.
Das Dashboard zeigt bereits Auswahlfehler, den Upload-Fortschritt
sowie Steuerelemente zum Abbrechen und erneuten Versuchen an. Der kleine Event-Handler ergänzt
Empfangsbestätigungen des Servers mit textContent, sodass Dateinamen als Text
angezeigt werden.
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import XHRUpload from '@uppy/xhr-upload'
import '@uppy/core/css/style.min.css'
import '@uppy/dashboard/css/style.min.css'
const receipts = document.querySelector('#receipts')
if (!(receipts instanceof HTMLUListElement)) throw new Error('Missing receipt list.')
const uppy = new Uppy({
autoProceed: false,
allowMultipleUploadBatches: false,
restrictions: {
maxFileSize: 2 * 1024 * 1024,
maxNumberOfFiles: 5,
allowedFileTypes: ['image/jpeg', 'image/png', '.pdf'],
},
})
.use(Dashboard, {
inline: true,
target: '#drag-drop-area',
note: 'Up to five JPEG, PNG, or PDF files, each up to 2 MiB.',
})
.use(XHRUpload, {
endpoint: '/upload',
fieldName: 'file',
formData: true,
bundle: false,
allowedMetaFields: false,
limit: 2,
shouldRetry: () => false,
onAfterResponse(xhr) {
if (xhr.status === 413) throw new Error('Choose a file no larger than 2 MiB.')
if (xhr.status === 415) throw new Error('The server requires a JPEG, PNG, or PDF MIME type.')
if (xhr.status < 200 || xhr.status >= 300) {
throw new Error('The server rejected the upload. Check the endpoint before retrying.')
}
},
})
uppy.on('upload-success', (file, response) => {
if (!file) return
const item = document.createElement('li')
item.textContent = `${file.name}: ${JSON.stringify(response.body)}`
receipts.append(item)
})
fieldName entspricht single('file') beim Empfänger.
allowedMetaFields: false lässt die standardmäßigen Metadatenfelder von Uppy weg, um das
Serverlimit von null Feldern einzuhalten. Setzen Sie den Header Content-Type
nicht selbst: Der Browser liefert die Multipart-Boundary. Zwei Dateien können gleichzeitig
übertragen werden; die übrigen Dateien warten in der Warteschlange von Uppy.
Erstellen Sie das Bundle und testen Sie einen Upload
Erstellen Sie im selben übergeordneten Verzeichnis, in dem Sie die Einrichtung ausgeführt haben, das Browser-Bundle und starten Sie den Server:
(
cd uppy-xhr-demo &&
corepack yarn exec esbuild client.ts --bundle --format=esm --outfile=public/app.js &&
node server.ts
)
esbuild erzeugt sowohl public/app.js als auch public/app.css.
Das CSS benötigt einen eigenen Link im HTML,
der in der Seite oben enthalten ist. Ein erneuter Build ersetzt diese beiden erzeugten Dateien.
Stoppen Sie den im Vordergrund laufenden Server vor dem erneuten Build mit Ctrl+C und laden Sie
die Seite nach dem Neustart neu.
Öffnen Sie genau die vom Server ausgegebene Adresse. Wählen Sie Dateien mit
browse files aus und nutzen Sie dann die
Upload-Schaltfläche des Dashboards. Bei einer ausgewählten Datei lautet ihre Beschriftung
Upload 1 file. Jede akzeptierte Datei ergänzt
unter Received by the server eine Empfangsbestätigung
mit bytes und sha256.
Eine leere Datei mit PDF-Dateinamen ist eine gültige Eingabe für diese Übertragungsdemo und erhält
bytes: 0; dadurch wird sie nicht zu einem gültigen PDF-Dokument.
Das Beispiel verarbeitet jeweils eine Gruppe ausgewählter Dateien. Sobald der Upload beginnt, wird jede weitere Auswahl gesperrt. Versuchen Sie fehlgeschlagene Uploads dieser Gruppe erneut oder laden Sie die Seite nach Abschluss neu, um eine neue Gruppe auszuwählen. Ein Abbruch leert die Auswahl, sodass Sie erneut wählen können. Bereits angezeigte Empfangsbestätigungen bleiben bis zum Neuladen erhalten.
Ein Fortschrittsbalken bei 100 % beschreibt die Übertragung, nicht die Annahme durch den Server.
Nur upload-success fügt eine Empfangsbestätigung hinzu. Werten Sie in Ihrer
eigenen Integration das Uppy-Ereignis complete
nicht als Beleg dafür, dass alle Dateien erfolgreich hochgeladen wurden: Es wird auch bei
fehlgeschlagenen Uploads ausgelöst und liefert getrennte Arrays namens
successful und failed.
Behalten Sie die Validierung auf dem Server bei
Clientseitige Beschränkungen helfen, Fehler vor dem Senden der Daten zu erkennen. Ein anderer Client kann sie umgehen. Dieser Empfänger begrenzt Anfragegröße und Dateianzahl separat, doch seine MIME-Prüfung vertraut weiterhin den vom Aufrufer gelieferten Metadaten. Er rendert die hochgeladenen Bytes niemals, führt sie nicht aus und liefert sie auch nicht wieder aus.
Bevor Sie das Beispiel für einen öffentlichen Dienst anpassen, ergänzen Sie Authentifizierung und Autorisierung, eine Inhaltsvalidierung für Ihre akzeptierten Formate und eine für Ihre Anwendung geeignete Speicherung. Begrenzen Sie dort auch Anfragen und gleichzeitige Zugriffe: Kleine Limits pro Datei begrenzen nicht den gesamten Speicherbedarf vieler gleichzeitig aktiver Clients. Die Demo bindet sich nur an die lokale Loopback-Adresse und bietet weder Authentifizierung noch dauerhafte Speicherung.
Häufige Probleme beheben
CORS-Konfiguration
Rufen Sie die Seite über die ausgegebene HTTP-Adresse auf, statt index.html
als lokale Datei zu öffnen. Die Seite und /upload haben denselben
Ursprung, daher benötigt dieses Beispiel keine CORS-Middleware. Wenn Sie die API an einen
anderen Ursprung verlegen, konfigurieren Sie diesen Server so, dass er den tatsächlichen
Ursprung Ihres Frontends und die erforderlichen Header zulässt.
CORS steuert den Browserzugriff auf Antworten;
es authentifiziert keine Upload-Anfrage.
Netzwerkfehler
Dieses Beispiel deaktiviert automatische Retries mit shouldRetry: () => false, damit
jeder Versuch nachvollziehbar ist. Stoppen Sie den Server nach dem Laden der Seite und versuchen
Sie einen Upload, um den Fehlerzustand des Dashboards zu sehen. Starten Sie ihn auf demselben
Port neu oder laden Sie die Seite unter seiner neuen Adresse und wählen Sie die Dateien erneut aus.
Bei einer fehlgeschlagenen Anfrage am selben Endpunkt beheben Sie die Ursache und wählen Retry. XHRUpload sendet die Datei von Anfang an erneut. Geht eine Antwort verloren, kann der Browser im Ungewissen bleiben, selbst wenn der Server die Bytes empfangen hat. Ein Upload-Dienst mit dauerhafter Speicherung benötigt eigene Regeln für den Umgang mit Duplikaten.
Drosseln Sie die Netzwerkgeschwindigkeit im Browser und verwenden Sie eine größere, zulässige Datei, um das Steuerelement Cancel in der Statusleiste während der laufenden Übertragung auszuprobieren. Ein Abbruch beendet die noch ausstehenden Anfragen des Browsers und leert die Auswahl; bereits vom Server empfangene Bytes kann er nicht zurückholen. Dies ist keine Funktion zum Pausieren und Fortsetzen.
Dateitypvalidierung
Eine Datei über dem clientseitigen Limit, eine nicht unterstützte Dateiendung oder ein nicht
unterstützter Dateityp sowie eine sechste Datei führen zu einem Auswahlfehler im Dashboard,
ohne den Upload dieser Datei zu starten. Die Regel .pdf erlaubt die
Dateiendung; sie untersucht keine PDF-Inhalte. HTTP 415 bedeutet, dass der Empfänger den
angegebenen MIME-Typ abgelehnt hat. HTTP 413 bedeutet, dass sein Größenlimit überschritten wurde.
Untersuchen Sie den POST im Netzwerkbereich der Browser-Entwicklertools; Änderungen an den
CORS-Einstellungen beheben diese Antworten nicht.
Nutzen Sie tus, wenn Uploads fortsetzbar sein müssen
XHRUpload eignet sich gut für gewöhnliche Multipart-Endpunkte und kleine Dateien. Eine Änderung des Limits für gleichzeitige Übertragungen ergänzt weder eine Aufteilung in Blöcke noch die Möglichkeit zum Fortsetzen. Für Uploads, die nach einer Unterbrechung fortgesetzt werden müssen, verwenden Sie das Tus-Plugin von Uppy mit einem tus-kompatiblen Server. Dabei kommt ein anderes Protokoll zum Einsatz, das den Austausch dieses Multipart-Empfängers erfordert.
