Friedrich Siever

19. November 2023

Ein Blick auf die Kernarchitektur von Strapi

von: Friedrich Siever | Last Updated: 12.01.24

In diesem Artikel wagen wir einen Blick unter die Haube von Strapi. Das ist wichtig, wenn wir die Kernfunktionalitäten von Strapi anpassen und erweitern wollen - etwa um einen Bereich für Premium-Content.

Falls Du erstmal wissen willst, was es mit Strapi grundsätzlich auf sich hat, empfehle ich Dir unseren Artikel Einstieg in Strapi vorab zu lesen.

Der Request Flow von Strapi

HTTP Request

Der Request-Flow beginnt, wenn eine HTTP-Anfrage an den Strapi-Server gesendet wird. Diese Anfrage kann in Form von GET, POST, PUT oder DELETE erfolgen und ist nicht auf Websites beschränkt. Sie kann auch von Apps oder IoT-Geräten ausgelöst werden. Dies unterstreicht den Vorteil des Multichanneling in Headless CMS-Systemen. Headless CMS-Systeme wie Strapi bieten die Flexibilität, Content in einer Vielzahl von Anwendungsfällen zu nutzen und in verschiedene Plattformen und Geräte zu integrieren.

Policies

Die Policies sind eine wichtige Komponente im Request-Flow von Strapi. Sie werden verwendet, um Berechtigungen und Zugriffssteuerungen für bestimmte Aktionen oder Endpunkte festzulegen. Policies können Authentifizierung, Autorisierung und andere Sicherheitsaspekte behandeln, um sicherzustellen, dass Anfragen den richtigen Sicherheitsrichtlinien entsprechen. Dies trägt zur Sicherheit und zum Schutz der Daten in Strapi bei.

Routing

Der Strapi-Server verwendet das Routing-System, um die empfangene Anfrage an die entsprechende Route oder den Controller weiterzuleiten. Das Routing basiert auf den in Strapi definierten Routen.

Middleware

Auf dem Weg zur entsprechenden Route passiert die Anfrage verschiedene Middleware-Funktionen. Middleware sind Funktionen, die vor oder nach der Verarbeitung der Anfrage ausgeführt werden. Sie können für Authentifizierung, Validierung und andere Aufgaben verwendet werden.

Controller

Sobald die Anfrage die richtige Route erreicht hat, wird sie an den entsprechenden Controller weitergeleitet. Der Controller ist für die Verarbeitung der Anfrage verantwortlich. Er kann auf verschiedene Dienste (Services) und Webhooks zurückgreifen, um die erforderlichen Aufgaben durchzuführen. Dienste sind Funktionen oder Module, die spezifische Geschäftslogik implementieren und dem Controller zur Verfügung stehen, um die Anfrage zu bearbeiten. In Strapi regeln diese Services normalerweise auch die Interaktion mit der Datenbank und sind von herausragender Bedeutung für das Framework. Webhooks ermöglichen die Integration externer Dienste oder Anwendungen, um zusätzliche Aufgaben auszuführen oder Daten auszutauschen. Die Controller sind im Request-Flow entscheidend, da hier die eigentliche Verarbeitung der Anfrage stattfindet.

Antwort senden

Schließlich sendet der Strapi-Server die Antwort an den Client, der die ursprüngliche Anfrage gestellt hat.

Models:

Die Models definieren die Datenstruktur und das Schema der in der Datenbank gespeicherten Informationen. Sie spielen eine entscheidende Rolle bei der Validierung, dem Zugriff und der Verwaltung von Daten. Der Controller kann auf die Models zugreifen, um Daten in der Datenbank zu erstellen, abzurufen, zu aktualisieren oder zu löschen. Die Models bilden die Grundlage für die Datenverarbeitung in Strapi und stellen sicher, dass die gespeicherten Informationen den definierten Regeln und Anforderungen entsprechen. Sie entsprechen im Wesentlichen den Content Types, die mit dem Content Type Builder festgelegt werden.

Eine Entsprechung der von uns mit Content Type Builder erstellten Models finden wir im Code von Strapi wieder. Für Posts zum Beispiel unter ** api/post/content-types/schema.json **.

Es kann für erfahrene Code-Liebhaber auch einfacher sein, die Modelle direkt im Code zu verwalten. Eine komplette Anleitung findest du in den Docs zu den Strapi-Modellen

Praxistipp: In der Praxis habe ich festgestellt, dass die Funktionen des Content-Builders in den meisten Fällen ausreichen, um Content Types einfach zu erstellen und zu gestalten. Daher greife ich bei diesen Aufgaben nur selten oder in speziellen Fällen auf die Anpassung durch Code zurück.

Die Gruppierung verschiedener Content Types, insbesondere wenn spezifische Strukturierungen erforderlich sind, kann nur durch die Bearbeitung des Codes umgesetzt werden. Zum Beispiel, wenn wir Kategorien erstellen möchten, die ausschließlich unter der Post-API zu finden sind. Dies verdeutlicht, dass die Konzepte der API und der Models zwar eng miteinander verbunden sind und oft synonym verwendet werden, aber dennoch getrennt sind.

CTX (Request und Response Koa)

Im Zusammenhang mit Strapi spielt das “ctx”-Objekt eine wichtige Rolle. Dieser Ansatz stammt ursprünglich aus dem zugrunde liegenden Koa.js-Framework. Koa kapselt die Anfrage und die Antwort in einem einzigen Objekt namens “Context”, das häufig als “ctx” abgekürzt wird. “ctx” bzw. Context bezieht sich auf den Kontext von Anfragen und Antworten, der im Rahmen einer HTTP-Anfrage und -Antwort in Strapi verwendet wird. Hier sind einige Schlüsselaspekte:

Request-Kontext (ctx.request)

Dieser Teil des “ctx”-Objekts enthält Informationen über die eingehende HTTP-Anfrage, wie z.B. HTTP-Methoden (GET, POST, PUT, DELETE), URL-Parameter, Anfrage-Header und Anfragekörper (Request Body). Entwickler können auf diese Informationen zugreifen, um die Anfrage zu analysieren und entsprechend zu reagieren. Zum Beispiel:

  • extrahieren wir die query parameter als ctx.query (Filter, Sortierung usw.)
  • Wir können auch Nutzer Informationen darüber erhalten, wer die Query gemacht hat. Das erfolgt über ctx.state.user
  • uvm

Response-Kontext (ctx.response)

Hierbei handelt es sich um den Teil des “ctx”-Objekts, der Informationen zur Erstellung der HTTP-Antwort enthält. Dies umfasst Dinge wie den Statuscode (z.B. 200 OK, 404 Not Found), die Antwort-Header und den Antwortkörper (Response Body). Entwickler können den Response-Kontext nutzen, um die HTTP-Antwort zu gestalten und sie an den Client zurückzusenden.

Lifecycle Hooks

Lifecycle Hooks sind Funktionen, die von Strapi ausgelöst werden, wenn bestimmte Aktionen, wie das Erstellen, Aktualisieren oder Löschen von Daten, in der Datenbank ausgeführt werden. Diese Hooks werden durch Lifecycle-Events verfügbar gemacht und bieten Entwicklern die Möglichkeit, benutzerdefinierte Logik auszuführen, bevor oder nachdem bestimmte Datenbankoperationen stattfinden.

Einige Beispiele für Lifecycle Hooks sind:

  • beforeCreate: Wird vor dem Erstellen eines Datensatzes aufgerufen.
  • beforeUpdate: Wird vor dem Aktualisieren eines Datensatzes aufgerufen.
  • afterCreate: Wird nach dem Erstellen eines Datensatz aufgerufen.

Das ist besonders wertvoll, wenn wir externe Applikationen wie unser Frontend über gewisse Änderungen oder Erweiterungen informieren wollen. An dieser Stelle möchte ich dir ein Beispiel für die deklarative Nutzung dieser Lifecycle Hooks zeigen.

// ./src/api/[api-name]/content-types/[content-type-name]/lifecycles.js
module.exports = {
  beforeCreate(event) {
    const { data, where, select, populate } = event.params;

    // Jedes Mal einen Rabatt von 15% geben.
    event.params.data.price = event.params.data.price * 0.85;
  },

  afterCreate(event) {
    const { result, params } = event;

    // do something to the result;
  },
};

Diese Lifecycle Hooks bieten eine leistungsstarke Möglichkeit, die Funktionalität deiner Strapi-Anwendung anzupassen und zu erweitern. Du kannst benutzerdefinierte Logik in diesen Hooks implementieren, um sicherzustellen, dass deine Anwendung genau das tut, was du benötigst. Aber beachte, dass diese Lifecycle Hooks eher dazu da sind, interne Operationen durchzuführen. Sie dürfen nicht mit Webhooks verwechselt werden, die dazu gedacht sind andere Applikationen wie z.B. dein Next.js (Stichwort ISR) oder React Frontend über gewisse Events zu informieren.

Routes

Die Konzepte von Routes sind von entscheidender Bedeutung, um die Interaktion mit deiner Strapi-Anwendung zu verstehen. Routes sind im Wesentlichen die Endpunkte oder URLs, gegen die du HTTP-Anfragen senden kannst, um Daten zu erstellen, abzurufen, zu aktualisieren oder zu löschen.

Hier sind einige wichtige Punkte zu Routes:

  1. Standard-CRUD-Routes: Strapi erstellt automatisch Standard-Routes für die CRUD-Operationen (Create, Read, Update, Delete) für jedes Datenmodell in deiner Anwendung. Das bedeutet, dass du mit Erstellung eines Datenmodells bereits automatisch Endpunkte zum Erstellen, Abrufen, Aktualisieren und Löschen von Daten hast.

  2. Anpassung der Core-Routes: Obwohl Strapi die Core-Routes automatisch generiert, kannst du diese nach deinen Anforderungen anpassen. Dies ermöglicht es dir eine spezifische Logik oder Sicherheitsprüfungen hinzuzufügen, bevor die Daten bearbeitet werden.

  3. Eigenständige Routes: Neben den standardmäßigen CRUD-Routes kannst du auch völlig eigenständige Routes erstellen. Dies bedeutet, dass du benutzerdefinierte Endpunkte erstellen kannst, die nicht an ein bestimmtes Datenmodell gebunden sind. Das ermöglicht dir die Implementierung spezieller Funktionen oder die Integration von Drittanbieterdiensten.

Insgesamt bieten Routes in Strapi eine flexible Möglichkeit die Interaktion mit deiner API zu steuern und anzupassen. Sie sind der Schlüssel zur Bereitstellung von Daten und Diensten über HTTP-Anfragen und ermöglichen es dir deine Anwendung genau nach deinen Anforderungen zu gestalten.

Jede Route ist durch ihre assoziierten Funktionen charakterisiert. Hierzu gehören Policies, Middlewares und Controller. Nochmal kurz zur Erinnerung:

  • Policies: Policies sind Sicherheitsrichtlinien, die den Zugriff auf bestimmte Routen und Endpunkte regulieren. Sie werden verwendet, um festzulegen, wer auf bestimmte Resourcen zugreifen kann und welche Berechtigungen erforderlich sind.

  • Middlewares: Middlewares sind Zwischenschritte in der Verarbeitung einer Anfrage. Sie können verwendet werden, um die Anfrage oder die Antwort zu manipulieren, bevor sie den Controller erreicht. Middlewares sind nützlich, um Aufgaben wie Authentifizierung, Validierung oder Protokollierung durchzuführen.

  • Controller: Der Controller ist für die eigentliche Verarbeitung der Anfrage verantwortlich. Er ruft geeignete Services auf, um Daten abzurufen, zu erstellen, zu aktualisieren oder zu löschen. Der Controller ist das Herzstück der Anwendungslogik und führt die spezifischen Aufgaben aus, die für die jeweilige Route erforderlich sind.

Zusammen bilden diese Funktionen den Prozess, wie Anfragen in Strapi verarbeitet werden. Sie ermöglichen die Steuerung des Zugriffs, die Verarbeitung der Daten und die Ausführung von Geschäftslogik in einer strukturierten und flexiblen Weise.

Eine existierende Route anpassen.

// "\src\api\value-tag\routes\value-tag.js"
'use strict';

/\*\*

- value-tag router \*/

const { createCoreRouter } = require('@strapi/strapi').factories; // //
CUSTOMIZATION VIA OPTIONS Object module.exports =
createCoreRouter('api::value-tag.value-tag', {

    prefix: '', // test --> test/tags
    only: ['find', 'findOne'], // create, update, delete werden deaktiviert
    except: [], // Gegenteil von only
    // Granulare Einstellungen für jede Route können hier vorgenommen werden.

    config: {
        find: {
          // default auth value is true -> with false we disable the strapi JWT auth system.
            auth: false,
            policies: [],
            middlewares: [],
        },
        findOne: {},
        create: {},
        update: {},
        delete: {},
    }

});

Einen neuen Router kreieren via Code

// src\api\value\routes\custom-route.js
module.exports = {
  routes: [
    {
      method: "GET",
      path: "values/example",
      handler: "myCustomController.example",
      config: {
        // some config here
      },
    },
  ],
};

Policies

Policies sind Funktionen, die mit Routes verbunden sind und bei jedem Request ausgeführt werden, bevor er die angefragte Route überhaupt erreicht. Der wichtigste Anwendungsfall besteht darin zu überprüfen, ob der eingehende Request berechtigt ist, die Route zu erreichen oder nicht.

Policy Scopes

Es gibt verschiedene Bereiche, in denen Policies definiert und angewendet werden können:

  • Global Policies: Diese Policies befinden sich im Verzeichnis ./src/policies/ und gehören zum Strapi Core. Sie können mit jeder Route in der gesamten Anwendung verbunden werden, um die Anfrageberechtigung zu steuern.

  • API Policies: API-spezifische Policies können im Verzeichnis ./src/api/[api-name]/policies/ definiert werden und auf Content-Routes angewendet werden. Dies ermöglicht die Feinabstimmung der Anfrageberechtigung für bestimmte Datentypen und sorgt für zusätzliche Sicherheit und Kontrolle in Bezug auf den Zugriff auf Content-Routen.

  • Plugin Policies: Policies, die von Plugins eingeführt werden, befinden sich im Verzeichnis ./src/plugins/[plugin-name]/policies/. Sie können grundsätzlich wie globale Policies verwendet werden, um die Anfrageberechtigung für bestimmte Routen zu steuern. Dies ermöglicht die Integration zusätzlicher Sicherheitsfunktionen, die von Plugins bereitgestellt werden.

Eine Custom Policy - Beispiel

Ein einfaches Beispiel könnte so aussehen. Die Daten werden nur zurückgegeben, wenn die Rolle des Nutzers Admin ist. Ansonst wird ein forbidden in Form eines Policy Errors zurückgegeben.

// src\api\value\policies\is-admin.js
module.exports = (policyContext, config, { strapi }) => {
  const isAllowed =
    policyContext.state.user &&
    policyContext.state.user.role.name == "Administrator";

  if (isAllowed) {
    return true;
  }

  return false;
};

Angewendet wird die Policy zum über den route folder in der api:

// src/api/value/routes/value.js
const { createCoreRouter } = require("@strapi/strapi").factories;

module.exports = createCoreRouter("api::value.value", {
  config: {
    find: {
      policies: ["is-admin"],
    },
  },
});

Das ist zwar ganz nett, aber nicht generisch. Hier Hilft das config Objekt in der policy, was wir auch beim Aufruf nutzen können. Wir können den Code also verbssern:

A. Beim Aufruf

// src\api\value\routes\value.js
const { createCoreRouter } = require("@strapi/strapi").factories;

module.exports = createCoreRouter("api::value.value", {
  config: {
    find: {
      policies: [{ name: "check-role", config: { userRole: "Author" } }],
    },
  },
});

B. Die Policy selbst

Hier ist das MD noch ok

//src\api\value\policies\check-role.js
module.exports = (policyContext, config, { strapi }) => {
  const { userRole } = config;
  const isAllowed =
    policyContext.state.user && policyContext.state.user.role.name === userRole;

  if (isAllowed) {
    return true;
  }

  // Wenn die Bedingung nicht erfüllt ist, wird standardmäßig `false` zurückgegeben.
  return false;
};

Das Strapi Objekt

Im vorherigen Code finden wir das Strapi Objekt, das von herausragender Bedeutung ist. Das Strapi Objekt spielt jedoch nicht nur bei den Policies eine Rolle. Deshalb verdient es seinen eigenen Platz.

Das Strapi Objekt wird in Strapis Kernfunktionen, wie Policies, Controllern und Services, injiziert. Dies dient dazu, den Zugriff auf andere Funktionalitäten zu gewährleisten und den Code modularer und besser wiederverwendbar zu gestalten.

Oder anders gesagt: Das Strapi-Objekt erweitert Koa um Funktionen, die speziell für die Verwaltung von Inhalten und Datenbankinteraktionen in Strapi entwickelt wurden. Das Strapi-Objekt ist eine wichtige Komponente für die Entwicklung von Strapi-Anwendungen und ermöglicht die nahtlose Verwendung von Content-Management-Funktionen.

Das Strapi Objekt ist wirklich mächtig. Aus meiner Sicht sind besonders wichtig:

  • Alle Services
  • Alle Controllers
  • Alle Routes
  • Alle Content Types
  • Alle Komponenten
  • Datenbankinformationen mit allen Tabellen
  • Entity Manager und Services

Die Tatsache, dass das Strapi-Objekt auf all diese Schlüsselelemente zugreift, unterstreicht seine Rolle bei der Erleichterung der Anwendungsentwicklung und Datenverwaltung in Strapi.

Middleware in Strapi

In Strapi sind Middleware-Funktionen integraler Bestandteil des Request-Flusses. Diese Funktionen werden nachdem der Request die Route erreicht hat, ausgeführt und dienen dazu, den Ablauf des Requests zu beeinflussen und zu modifizieren. Sie fügen zusätzliche Funktionen hinzu, die zwar für die Verarbeitung von Requests nützlich sind, aber nicht unbedingt auf alle Requests angewendet werden müssen.

Wichtig ist, dass Middleware von der Kern-Business-Logik getrennt ist, die normalerweise vom Controller übernommen wird. Sie können als Ergänzung zur Hauptlogik betrachtet werden, um spezielle Aufgaben wie Authentifizierung, Logging oder Validierung auszuführen.

Ein entscheidender Punkt ist, dass es verschiedene Arten von Middleware gibt, die zu unterschiedlichen Zeitpunkten im Request-Flow ausgeführt werden. Einige Middleware-Funktionen werden vor dem Erreichen des Controllers ausgeführt, während andere erst danach aktiv werden. Dies ermöglicht eine präzise Steuerung und Anpassung des Request-Verarbeitungsprozesses in Strapi.

In Strapi werden die Core-Middleware-Funktionen standardmäßig aus einer Datei im Konfigurationsordner automatisch exportiert. Diese Core-Middleware ist wesentlich für das ordnungsgemäße Funktionieren von Strapi und sollte normalerweise nicht entfernt werden.

Wenn du jedoch eigene Middleware-Funktionen erstellen möchtest, musst du diese zu dem Middleware-Array hinzufügen, das normalerweise am Ende des Middleware-Arrays platziert wird. Wenn du dies nicht tust, wird deine benutzerdefinierte Middleware ignoriert und nicht im Request-Flow berücksichtigt.

Das bedeutet, dass du die Reihenfolge der Middleware-Funktionen berücksichtigen musst. Core-Middleware hat Vorrang und sollte in der Regel nicht geändert oder gelöscht werden, es sei denn, du hast spezifische Anforderungen, die eine Anpassung erfordern. Wenn du benutzerdefinierte Middleware hinzufügst, stelle sicher, dass sie in der Reihenfolge, in der sie benötigt werden, platziert sind, damit sie wie erwartet funktionieren.

Unterschied zwischen Middleware und Lifecycle Hooks in Strapi

Die Middleware und die Lifecycle Hooks in Strapi dienen beide dazu, den Request-Flow und die Verarbeitung von Anfragen zu steuern und zu modifizieren, aber sie haben unterschiedliche Zwecke und Zeitpunkte, zu denen sie ausgeführt werden.

Middleware:

Middleware sind Funktionen, die nachdem der Request die Route erreicht hat, ausgeführt werden. Sie dienen dazu, den Request-Flow zu beeinflussen und zu modifizieren. Middleware kann auf alle Requests angewendet werden, muss es aber nicht. Sie werden verwendet, um zusätzliche Funktionen wie Authentifizierung, Logging oder Validierung hinzuzufügen.

  • Middleware-Funktionen werden in einem bestimmten Abwicklungspunkt im Request-Flow ausgeführt, bevor die Anfrage den Controller erreicht.
  • Middleware kann global oder auf Route- oder API-Ebene definiert werden und bietet Flexibilität bei der Anwendung auf verschiedene Teile der Anwendung.

Lifecycle Hooks:

Lifecycle Hooks sind Funktionen, die bei bestimmten Lifecycle-Events ausgelöst werden, wenn CRUD-Operationen in Strapi (wie Erstellen, Aktualisieren oder Löschen von Daten) durchgeführt werden. Sie sind eng mit den Models und den Datenbankoperationen verknüpft. Jedes Lifecycle-Event ist an eine spezifische CRUD-Operation gebunden und kann benutzerdefinierte Logik vor oder nach diesen Operationen ausführen.

  • Lifecycle Hooks sind besonders nützlich, um zusätzliche Validierungen, Datenmanipulationen oder die Integration externer Dienste bei Datenänderungen auszuführen.
  • Diese Hooks sind spezifisch für CRUD-Vorgänge und werden auf Model-Ebene definiert.

In Kürze ausgedrückt, Middleware sind allgemeine Funktionen, die den Request-Flow beeinflussen und auf die meisten Requests angewendet werden können, während Lifecycle Hooks spezifische Funktionen sind, die mit den CRUD-Operationen von Models verknüpft sind und bei Datenänderungen ausgelöst werden. Beide sind nützliche Instrumente in Strapi, aber sie werden in unterschiedlichen Kontexten und für verschiedene Zwecke verwendet.

Strapi Controller

Strapi-Controller sind die Kernkomponenten. Sie sind für die Verwaltung der Geschäftslogik verantwortlich. Und sie sind eng mit den Routen verknüpft. Die Controller sind in Aktionen organisiert, die einzelne Funktionen oder Operationen repräsentieren. Sie können APIs zugeordnet oder mithilfe von Plugins generiert werden.

Strapi bietet die Flexibilität, benutzerdefinierte Controller zu erstellen oder bestehende zu erweitern oder zu ersetzen. Alle Controller sind über das Strapi-Objekt zugänglich und können mithilfe der Methode strapi.controller() aufgerufen werden.

Die Organisation in Aktionen und die Integration mit Routen machen Strapi-Controller zu einem leistungsstarken Werkzeug zur Verwaltung der Anwendungslogik, zur Verarbeitung von Anfragen und zur Verwaltung von Daten.

Strapi Controller und Services

Strapi-Controller nutzen Services, um Low-Level-Funktionen wie Datenbanktransaktionen durchzuführen. Dieser Ansatz trägt dazu bei, den Controller-Code sauber und effizient zu halten und Redundanzen zu vermeiden.

Services sind Module oder Funktionen, die spezifische Geschäftslogik implementieren und von Controllern verwendet werden, um Aufgaben wie das Erstellen, Aktualisieren, Lesen oder Löschen von Daten in der Datenbank zu bewältigen. Indem Services für diese Aufgaben wiederverwendet werden, wird der Controller-Code schlank und gut wartbar.

Die Trennung von Controller und Service ermöglicht eine klare Strukturierung der Anwendungslogik und fördert bewährte Praktiken in der Entwicklung von Strapi-Anwendungen.

Das führt uns direkt zu den Services.

Services in Strapi

In Strapi werden Services für Datenbankoperationen und Business-Logik verwendet. Controller verwenden Services, um Anfragen zu verarbeiten. Services sorgen für klare Verantwortlichkeiten und erleichtern die Skalierbarkeit der Anwendung.

  • Services sind wiederverwendbare Hilfsfunktionen, die spezifische Aufgaben erfüllen.
  • Sie vereinfachen den Code der Controller und fördern das DRY-Prinzip.
  • Ein typischer Anwendungsfall ist das Abfragen von Entitäten aus der Datenbank, beispielsweise von Beiträgen (Posts).
  • Services führen die Datenbankabfragen nicht direkt aus; hierfür benötigen sie die Entity Service API, die wiederum auf der Query Engine API aufbaut.
  • Ähnlich wie Controller gehören Services zu APIs oder Plugins und können angepasst, ersetzt oder erweitert werden.
  • Services sind über Controller erreichbar, aber auch von anderen Services aus, indem die strapi.service() Funktion verwendet wird.

Services im Überblick

FunktionenVerantwortlichkeitAccessibility
ServicesWiederverwertbare einzelne Tasksstrapi.service(api::post.post).myService(args)
Entity Service APIDatenoperationen, Verwaltung komplexer Datenstrukturen (Komponenten Dynamic Zones)strapi.entityService.findOne(api:post.post, entityId, this.getFetchParams(params))
Query Engine APINiedrigste Ebene Datenoperationen gegen die Datenbank (kennt keine komplexen Strukturen)strapi.db.query(api:post.post).findMany()
0
0 Bewertungen

Jetzt selbst bewerten