middlewarevalidatorservernext zurückgebennext an die nächste Middleware weitergebenclientMiddleware ermöglicht es Ihnen, das Verhalten von Serverfunktionen, die mit createServerFn erstellt wurden, mit Dingen wie gemeinsam genutzter Validierung, Kontext und vielem mehr anzupassen. Middleware kann sogar von anderer Middleware abhängen, um eine Kette von Operationen zu erstellen, die hierarchisch und geordnet ausgeführt werden.
Middleware wird mit der Funktion createMiddleware definiert. Diese Funktion gibt ein Middleware-Objekt zurück, das verwendet werden kann, um die Middleware weiter mit Methoden wie middleware, validator, server und client anzupassen.
import { createMiddleware } from '@tanstack/solid-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next, data }) => {
console.log('Request received:', data)
const result = await next()
console.log('Response processed:', result)
return result
},
)
import { createMiddleware } from '@tanstack/solid-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next, data }) => {
console.log('Request received:', data)
const result = await next()
console.log('Response processed:', result)
return result
},
)
Sobald Sie Ihre Middleware definiert haben, können Sie sie in Kombination mit der Funktion createServerFn verwenden, um das Verhalten Ihrer Serverfunktionen anzupassen.
import { createServerFn } from '@tanstack/solid-start'
import { loggingMiddleware } from './middleware'
const fn = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
// ...
})
import { createServerFn } from '@tanstack/solid-start'
import { loggingMiddleware } from './middleware'
const fn = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
// ...
})
Mehrere Methoden stehen zur Verfügung, um die Middleware anzupassen. Wenn Sie TypeScript verwenden (hoffentlich), wird die Reihenfolge dieser Methoden durch das Typsystem erzwungen, um maximale Inferenz und Typsicherheit zu gewährleisten.
Die Methode middleware wird verwendet, um Abhängigkeits-Middleware an die Kette anzuhängen, die vor der aktuellen Middleware ausgeführt wird. Rufen Sie einfach die Methode middleware mit einem Array von Middleware-Objekten auf.
import { createMiddleware } from '@tanstack/solid-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([
authMiddleware,
loggingMiddleware,
])
import { createMiddleware } from '@tanstack/solid-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).middleware([
authMiddleware,
loggingMiddleware,
])
Typsichere Kontext- und Payload-Validierung werden ebenfalls von übergeordneten Middleware geerbt!
Die Methode validator wird verwendet, um das Datenobjekt zu modifizieren, bevor es an diese Middleware, verschachtelte Middleware und letztendlich die Serverfunktion weitergegeben wird. Diese Methode sollte eine Funktion empfangen, die das Datenobjekt entgegennimmt und ein validiertes (und optional modifiziertes) Datenobjekt zurückgibt. Es ist üblich, eine Validierungsbibliothek wie zod dafür zu verwenden. Hier ist ein Beispiel
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const mySchema = z.object({
workspaceId: z.string(),
})
const workspaceMiddleware = createMiddleware({ type: 'function' })
.validator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const mySchema = z.object({
workspaceId: z.string(),
})
const workspaceMiddleware = createMiddleware({ type: 'function' })
.validator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
Die Methode server wird verwendet, um serverseitige Logik zu definieren, die die Middleware sowohl vor als auch nach jeder verschachtelten Middleware und letztendlich einer Serverfunktion ausführt. Diese Methode empfängt ein Objekt mit den folgenden Eigenschaften
Die Funktion next wird verwendet, um die nächste Middleware in der Kette auszuführen. Sie müssen das Ergebnis der Ihnen übergebenen Funktion next awaiten und zurückgeben (oder direkt zurückgeben), damit die Kette weiter ausgeführt werden kann.
import { createMiddleware } from '@tanstack/solid-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('Request received')
const result = await next()
console.log('Response processed')
return result
},
)
import { createMiddleware } from '@tanstack/solid-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('Request received')
const result = await next()
console.log('Response processed')
return result
},
)
Die Funktion next kann optional mit einem Objekt aufgerufen werden, das eine context-Eigenschaft mit einem Objektwert hat. Welche Eigenschaften Sie an diesen context-Wert übergeben, wird mit dem übergeordneten context zusammengeführt und an die nächste Middleware weitergegeben.
import { createMiddleware } from '@tanstack/solid-start'
const awesomeMiddleware = createMiddleware({ type: 'function' }).server(
({ next }) => {
return next({
context: {
isAwesome: Math.random() > 0.5,
},
})
},
)
const loggingMiddleware = createMiddleware({ type: 'function' })
.middleware([awesomeMiddleware])
.server(async ({ next, context }) => {
console.log('Is awesome?', context.isAwesome)
return next()
})
import { createMiddleware } from '@tanstack/solid-start'
const awesomeMiddleware = createMiddleware({ type: 'function' }).server(
({ next }) => {
return next({
context: {
isAwesome: Math.random() > 0.5,
},
})
},
)
const loggingMiddleware = createMiddleware({ type: 'function' })
.middleware([awesomeMiddleware])
.server(async ({ next, context }) => {
console.log('Is awesome?', context.isAwesome)
return next()
})
Obwohl Serverfunktionen hauptsächlich serverseitig gebundene Operationen sind, gibt es immer noch viele clientseitige Logiken, die die ausgehende RPC-Anfrage vom Client umgeben. Das bedeutet, dass wir auch clientseitige Logiken in Middleware definieren können, die auf der Clientseite um jede verschachtelte Middleware und letztendlich die RPC-Funktion und ihre Antwort an den Client ausgeführt wird.
Standardmäßig wird die Middleware-Validierung nur auf dem Server durchgeführt, um die Größe des Client-Bundles klein zu halten. Sie können jedoch auch Daten auf der Clientseite validieren, indem Sie die Option validateClient: true an die Funktion createMiddleware übergeben. Dies bewirkt, dass die Daten auf der Clientseite validiert werden, bevor sie an den Server gesendet werden, was potenziell einen Hin- und Rückweg spart.
Warum kann ich kein anderes Validierungsschema für den Client übergeben?
Das clientseitige Validierungsschema wird vom serverseitigen Schema abgeleitet. Dies liegt daran, dass das clientseitige Validierungsschema verwendet wird, um die Daten zu validieren, bevor sie an den Server gesendet werden. Wenn das clientseitige Schema vom serverseitigen Schema abweicht, würde der Server Daten erhalten, die er nicht erwartet hat, was zu unerwartetem Verhalten führen könnte.
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const workspaceMiddleware = createMiddleware({ validateClient: true })
.validator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
import { createMiddleware } from '@tanstack/solid-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const workspaceMiddleware = createMiddleware({ validateClient: true })
.validator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
Client-Middleware-Logik wird mit der Methode client auf einem Middleware-Objekt definiert. Diese Methode wird verwendet, um clientseitige Logik zu definieren, die die Middleware sowohl vor als auch nach jeder verschachtelten Middleware und letztendlich der clientseitigen RPC-Funktion (oder der serverseitigen Funktion, wenn Sie SSR durchführen oder diese Funktion von einer anderen Serverfunktion aus aufrufen) ausführt.
Clientseitige Middleware-Logik teilt sich einen Großteil derselben API wie Logik, die mit der Methode server erstellt wurde, wird aber auf der Clientseite ausgeführt. Dazu gehören
Ähnlich wie die Funktion server empfängt sie auch ein Objekt mit den folgenden Eigenschaften
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
console.log('Request sent')
const result = await next()
console.log('Response received')
return result
},
)
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
console.log('Request sent')
const result = await next()
console.log('Response received')
return result
},
)
Client-Kontext wird standardmäßig NICHT an den Server gesendet, da dies unbeabsichtigt große Payloads an den Server senden könnte. Wenn Sie Client-Kontext an den Server senden müssen, müssen Sie die Funktion next mit einer Eigenschaft sendContext und einem Objekt aufrufen, um Daten an den Server zu übertragen. Alle Eigenschaften, die an sendContext übergeben werden, werden zusammen mit den Daten serialisiert und an den Server gesendet und sind im normalen Kontextobjekt jeder verschachtelten Server-Middleware verfügbar.
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
// Send the workspace ID to the server
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, data, context }) => {
// Woah! We have the workspace ID from the client!
console.log('Workspace ID:', context.workspaceId)
return next()
})
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
// Send the workspace ID to the server
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, data, context }) => {
// Woah! We have the workspace ID from the client!
console.log('Workspace ID:', context.workspaceId)
return next()
})
Sie haben vielleicht bemerkt, dass im obigen Beispiel, während clientseitig gesendeter Kontext typsicher ist, keine Laufzeitvalidierung erforderlich ist. Wenn Sie dynamische, benutzergenerierte Daten über den Kontext senden, könnte dies ein Sicherheitsproblem darstellen. Wenn Sie dynamische Daten vom Client über den Kontext an den Server senden, sollten Sie sie in der serverseitigen Middleware validieren, bevor Sie sie verwenden. Hier ist ein Beispiel
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, data, context }) => {
// Validate the workspace ID before using it
const workspaceId = zodValidator(z.number()).parse(context.workspaceId)
console.log('Workspace ID:', workspaceId)
return next()
})
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, data, context }) => {
// Validate the workspace ID before using it
const workspaceId = zodValidator(z.number()).parse(context.workspaceId)
console.log('Workspace ID:', workspaceId)
return next()
})
Ähnlich wie beim Senden von Client-Kontext an den Server können Sie auch Server-Kontext an den Client senden, indem Sie die Funktion next mit einer Eigenschaft sendContext und einem Objekt aufrufen, um Daten an den Client zu übertragen. Alle Eigenschaften, die an sendContext übergeben werden, werden zusammen mit der Antwort serialisiert und an den Client gesendet und sind im normalen Kontextobjekt jeder verschachtelten Client-Middleware verfügbar. Das zurückgegebene Objekt des Aufrufs von next in client enthält den vom Server an den Client gesendeten Kontext und ist typsicher. Middleware kann den vom Server an den Client gesendeten Kontext aus früheren Middleware, die von der Funktion middleware verknüpft wurden, ableiten.
Warnung
Der Rückgabetyp von next in client kann nur von Middleware abgeleitet werden, die in der aktuellen Middleware-Kette bekannt ist. Daher ist der genaueste Rückgabetyp von next in Middleware am Ende der Middleware-Kette.
const serverTimer = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
return next({
sendContext: {
// Send the current time to the client
timeFromServer: new Date(),
},
})
},
)
const requestLogger = createMiddleware({ type: 'function' })
.middleware([serverTimer])
.client(async ({ next }) => {
const result = await next()
// Woah! We have the time from the server!
console.log('Time from the server:', result.context.timeFromServer)
return result
})
const serverTimer = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
return next({
sendContext: {
// Send the current time to the client
timeFromServer: new Date(),
},
})
},
)
const requestLogger = createMiddleware({ type: 'function' })
.middleware([serverTimer])
.client(async ({ next }) => {
const result = await next()
// Woah! We have the time from the server!
console.log('Time from the server:', result.context.timeFromServer)
return result
})
Middleware, die die Methode server verwendet, wird im selben Kontext wie Serverfunktionen ausgeführt, sodass Sie die genauen Serverfunktions-Kontext-Dienstprogramme befolgen können, um Anforderungsheader, Statuscodes usw. zu lesen und zu modifizieren.
Middleware, die die Methode client verwendet, wird in einem völlig anderen clientseitigen Kontext als Serverfunktionen ausgeführt, sodass Sie nicht dieselben Dienstprogramme zum Lesen und Modifizieren der Anfrage verwenden können. Sie können die Anfrage jedoch weiterhin modifizieren, indem Sie zusätzliche Eigenschaften zurückgeben, wenn Sie die Funktion next aufrufen. Derzeit unterstützte Eigenschaften sind
Hier ist ein Beispiel für das Hinzufügen eines Authorization-Headers zu jeder Anforderung, die diese Middleware verwendet
import { getToken } from 'my-auth-library'
const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
},
)
import { getToken } from 'my-auth-library'
const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
},
)
Middleware kann auf zwei Arten verwendet werden
Globale Middleware wird automatisch für jede Serverfunktion in Ihrer Anwendung ausgeführt. Dies ist nützlich für Funktionen wie Authentifizierung, Protokollierung und Überwachung, die für alle Anfragen gelten sollen.
Um globale Middleware zu verwenden, erstellen Sie eine Datei global-middleware.ts in Ihrem Projekt (typischerweise unter app/global-middleware.ts). Diese Datei wird in Client- und Serverumgebungen ausgeführt und hier registrieren Sie globale Middleware.
Hier erfahren Sie, wie Sie globale Middleware registrieren
// app/global-middleware.ts
import { registerGlobalMiddleware } from '@tanstack/solid-start'
import { authMiddleware } from './middleware'
registerGlobalMiddleware({
middleware: [authMiddleware],
})
// app/global-middleware.ts
import { registerGlobalMiddleware } from '@tanstack/solid-start'
import { authMiddleware } from './middleware'
registerGlobalMiddleware({
middleware: [authMiddleware],
})
Typen von globaler Middleware sind inhärent von Serverfunktionen selbst getrennt. Das bedeutet, dass, wenn eine globale Middleware zusätzlichen Kontext für Serverfunktionen oder andere serverfunktionsspezifische Middleware bereitstellt, die Typen nicht automatisch an die Serverfunktion oder andere serverfunktionsspezifische Middleware weitergegeben werden.
// app/global-middleware.ts
registerGlobalMiddleware({
middleware: [authMiddleware],
})
// app/global-middleware.ts
registerGlobalMiddleware({
middleware: [authMiddleware],
})
// authMiddleware.ts
const authMiddleware = createMiddleware({ type: 'function' }).server(
({ next, context }) => {
console.log(context.user) // <-- This will not be typed!
// ...
},
)
// authMiddleware.ts
const authMiddleware = createMiddleware({ type: 'function' }).server(
({ next, context }) => {
console.log(context.user) // <-- This will not be typed!
// ...
},
)
Um dies zu lösen, fügen Sie die globale Middleware, auf die Sie verweisen möchten, dem Middleware-Array der Serverfunktion hinzu. Die globale Middleware wird zu einem einzigen Eintrag dedupliziert (die globale Instanz), und Ihre Serverfunktion erhält die korrekten Typen.
Hier ist ein Beispiel, wie das funktioniert
import { authMiddleware } from './authMiddleware'
const fn = createServerFn()
.middleware([authMiddleware])
.handler(async ({ context }) => {
console.log(context.user)
// ...
})
import { authMiddleware } from './authMiddleware'
const fn = createServerFn()
.middleware([authMiddleware])
.handler(async ({ context }) => {
console.log(context.user)
// ...
})
Middleware wird priorisiert ausgeführt, beginnend mit globaler Middleware, gefolgt von Serverfunktions-Middleware. Das folgende Beispiel wird in dieser Reihenfolge protokollieren
const globalMiddleware1 = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('globalMiddleware1')
return next()
},
)
const globalMiddleware2 = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('globalMiddleware2')
return next()
},
)
registerGlobalMiddleware({
middleware: [globalMiddleware1, globalMiddleware2],
})
const a = createMiddleware({ type: 'function' }).server(async ({ next }) => {
console.log('a')
return next()
})
const b = createMiddleware({ type: 'function' })
.middleware([a])
.server(async ({ next }) => {
console.log('b')
return next()
})
const c = createMiddleware({ type: 'function' })
.middleware()
.server(async ({ next }) => {
console.log('c')
return next()
})
const d = createMiddleware({ type: 'function' })
.middleware([b, c])
.server(async () => {
console.log('d')
})
const fn = createServerFn()
.middleware([d])
.server(async () => {
console.log('fn')
})
const globalMiddleware1 = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('globalMiddleware1')
return next()
},
)
const globalMiddleware2 = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('globalMiddleware2')
return next()
},
)
registerGlobalMiddleware({
middleware: [globalMiddleware1, globalMiddleware2],
})
const a = createMiddleware({ type: 'function' }).server(async ({ next }) => {
console.log('a')
return next()
})
const b = createMiddleware({ type: 'function' })
.middleware([a])
.server(async ({ next }) => {
console.log('b')
return next()
})
const c = createMiddleware({ type: 'function' })
.middleware()
.server(async ({ next }) => {
console.log('c')
return next()
})
const d = createMiddleware({ type: 'function' })
.middleware([b, c])
.server(async () => {
console.log('d')
})
const fn = createServerFn()
.middleware([d])
.server(async () => {
console.log('fn')
})
Die Funktionalität von Middleware wird je nach Umgebung für jedes produzierte Bundle aus dem Bundle entfernt (tree-shaken).
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.