Strapi introductie: Een blog bouwen met Strapi headless CMS & Next.js

Nyef
Nyef
Strapi introductie: Een blog bouwen met Strapi headless CMS & Next.js

In deze blogpost introduceer ik je aan Strapi, een headless CMS. We zullen een lokale blogsite bouwen met een Strapi back-end en Next.js front-end. De nadruk zal vooral liggen op Strapi en de datakoppeling. In principe is het front-end een bijzaak voor deze blogpost.

Het eindresultaat zal een simpele site zijn met:

  • Homepagina met pagination & filter
  • Blogpost pagina
  • Over ons pagina

Alle dynamische data zullen door onze Next.js server uit Strapi worden opgehaald:

Blogpost data in Strapi dashboard

Voordat we beginnen, is het handig om te weten wat Strapi is en waarom het wordt gebruikt.

Over Strapi

Kort gezegd is Strapi een open-source headless CMS-oplossing. Data die je gebruikt in jouw applicaties, kun je opslaan en beheren vanuit jouw Strapi dashboard. Met de Strapi API kun je o.a. CRUD-operaties uitvoeren.

Data van Strapi naar front-end

Strapi is geschikt om te gebruiken voor:

  • Statische websites
  • Mobiele applicaties
  • Bedrijfswebsites
  • E-commerce
  • Editorial sites

In deze blogpost gebruiken we het dus voor een editorial site, een blogwebsite.

Waarom Strapi gebruiken?

Een aantal redenen en voordelen om Strapi te gebruiken zijn:

  • Snelle (initiële) livegang: Je hoeft niet zelf een backend-applicatie te ontwikkelen, met bijvoorbeeld Spring Framework. Dit scheelt tijd én geld. Omdat het tijd scheelt, kun je dus sneller live gaan.
  • Flexibiliteit: Je kunt je eigen datastructuren definiëren en aanpassen.
  • Integratie: Strapi is headless, dus de front-end oplossing moet je zelf bouwen en koppelen. Je bent vrij om zelf je front-end technieken te kiezen. Zo kun je dus Next.js, Vue.js of wat dan ook gebruiken.
  • Snel te leren: Strapi geeft je een admin panel vanuit waar je jouw content en instellingen kunt beheren. De interface hiervoor is vrij intuïtief en niet al te complex.
  • Scheiding development & content management: Bij all-in-one oplossingen, zoals WordPress, moeten developers en content managers in hetzelfde systeem werken. Dit kan leiden tot situaties waarin beide elkaar in de weg zitten. Door het front-end te scheiden van het CMS, kun je dit enigszins voorkomen. Een developer kan werken aan het front-end zonder dat het effect heeft op de content managers.

Onderdelen van Strapi

Strapi bestaat uit drie onderdelen: development, users & deployment. In het volgende hoofdstuk komen deze onderdelen dieper aan bod, maar eerst een korte beschrijving.

Development

Als je dit leest is dit waarschijnlijk het meest interessante onderdeel voor jou. Het development onderdeel is namelijk het opzetten en laten draaien van jouw Strapi applicatie.

Users

Het users onderdeel heeft betrekking tot het Strapi admin panel. Vanuit deze panel kunnen content managers content toevoegen en beheren.

Deployment

Het onderdeel deployment, dat technisch gezien onder development valt, heeft betrekking op het online laten draaien van jouw applicatie. Je kunt Strapi zelf hosten of gebruik maken van de cloud service.

Aan de slag met Strapi

We gaan nu stap voor stap aan de slag met het opzetten van een Strapi applicatie en het integreren ervan in een Next.js website.

Alle basisonderdelen van Strapi zullen aan bod komen. Let wel op, Strapi kan ook zeer uitgebreid zijn, dus er is nog veel meer dan wat er in deze blogpost is beschreven.

Eisen om stappen te volgen

Als je de volgende stappen wilt meevolgen, ga ik ervan uit dat je voldoet aan de volgende eisen:

  • Het hebben van algemene programmeerkennis (met bijv. Javascript, Typescript, Node.js)
  • Kennis van RESTful API principes
  • Weten hoe je werkt met NPM (of Yarn) & de terminal

Inrichten Strapi headless CMS

In dit hoofdstuk zullen we een Strapi-applicatie opzetten en data in ons CMS-systeem toevoegen. Om dat te doen zullen we de volgende stappen volgen:

  1. Opzetten database
  2. Installeren Strapi-applicatie
  3. Eerste administrator toevoegen
  4. Content-types toevoegen
  5. Administrators toevoegen
  6. Eerste data toevoegen via het CMS

Database aanmaken

Strapi heeft een database nodig om data in op te slaan. In mijn geval kies ik om het in een PostgreSQL database op te slaan. Dus ik maak eerst lokaal op mijn machine de database strapi-blog-db aan.

Je hoeft niet per se een PostgeSQL database te gebruiken. Het mag ook een MySQL, MariaDB of SQLite database zijn (ten tijde van het schrijven van deze blogpost).

Opzetten van Strapi applicatie

Nu kunnen we daadwerkelijk aan de slag met Strapi. Laten we de applicatie aanmaken.

Installeren

Vanuit de terminal voer ik het installatiescript uit. Omdat ik de voorkeur heb om Typescript te gebruiken, voeg ik de flag ervoor toe (--typescript):

Bash (/terminal)

1npx create-strapi-app@latest strapi-blog --typescript
2# Need to install the following packages:
3# create-strapi-app@4.24.1
4# Ok to proceed? (y) y
5# ? Choose your installation type Custom (manual settings)
6# ? Choose your default database client postgres
7# ? Database name: strapi-blog-db
8# ? Host: 127.0.0.1
9# ? Port: 5432
10# ? Username: nyef
11# ? Password: 
12# ? Enable SSL connection: No
13#
14# Creating a project with custom database options.
15# Creating a new Strapi application at /Users/nyef/Repositories/strapi-nextjs-blog-tutorial/strapi-blog.
16# Creating files.

Ten tijde van het schrijven van deze blog, is de laatste versie van Strapi 4.24.1

Je kunt ook kiezen om --quickstart toe te voegen, maar dan kun je niet een database kiezen (standaard SQLite). Zie de documentatie voor alle installatie CLI-opties.

Ik kreeg overigens nog een foutmelding. In mijn geval werd er geprobeerd om yarn install uit te voeren terwijl ik npm gebruik. Dus ik moest zelf nog een npm install doen.

Als alles goed is gegaan, heb je nu een map met de naam van jouw project, in mijn geval strapi-blog. Daarin vind je alle mappen en bestanden van jouw Strapi project.

Starten

Je kunt nu de applicatie starten met npm run develop of npm run start. Die laatste is bedoeld om te starten in productie. Het voornaamste verschil is dat strapi develop autoReload en de content-type builder (hierover later meer) aan heeft (zie docs).

Administrator toevoegen

Zodra je de applicatie hebt gestart, kom je op een registratiepagina. Daar maak je de eerste administrator-account aan.

Strapi admin registreren

Als admin krijg je toegang tot de Strapi admin panel. Je kunt later meerdere administrators toevoegen. Een administrator kan content beheren. Strapi werkt met een rechtensysteem, op basis daarvan kun je aangeven wat iedere admin kan en mag doen, daarover straks meer.

De eerste admin is een zogenoemde Super Admin. Deze kan onder andere alle plugins en gebruikers beheren. Eén van die plugins is de Content-type Builder.

Strapi admin panel

Content-type Builder

De content-type builder is de core plugin van Strapi. Maar wat is een content-type in Strapi?

Zoals je inmiddels weet is Strapi een content managementsysteem (CMS). Oftewel, een systeem waarin je content voor je applicatie of website aanmaakt en bewerkt. Content kan van alles zijn — een blogpost, product, stukje tekst op de homepagina of een link in de footer, jij bent daar vrij in. En omdat jij daar vrij in bent, moet je het dus zelf definiëren. Dat doe je met de content-type builder.

Een content-type zou je kunnen vergelijken met een entity in JPA of een Typescript type. Iedere content-type heeft in elk geval een naam en wat velden.

Strapi kent drie soorten content-types:

  • Collection: Een content-type waar er meerdere instanties van kunnen zijn. Denk bijvoorbeeld aan een product of blogpost.
  • Single: Een content-type waar er maar één instantie van is. Bijvoorbeeld een stukje tekst "Over mij".
  • Component: Een content-type die opnieuw wordt gebruikt binnen collection- of single-types. Bijvoorbeeld SEO-metadata die je zowel aan een blogpost als aan de "Over mij"-pagina koppelt.

Een content-type zelf kan alleen door een admin worden beheerd. Let wel op, dit kun je alleen doen als je de applicatie in dev mode hebt gestart (npm run develop).

Laten we voor ons project nu een aantal content-types aanmaken. Via je admin dashboard, ga naar "Content-Type Builder". Hier zie je al één collection type: User. Deze laten we voor nu.

Voor ons voorbeeldproject maken we de volgende content-types aan (bij alle velden moet je via Advanced Settings aangeven dat het een Required field is, tenzij ik hieronder anders aangeef):

Component-type genaamd "SEO metadata":

Veld typeNaamOpmerking
Text field (long)description

Single-type genaamd "About us":

Veld typeNaamOpmerking
Text field (short)title
Text field (long)description
ComponentseoMetadataKies voor Existing -> SEO metadata -> Single.

Collection-type genaamd "Author":

Veld typeNaamOpmerking
Text field (short)name
Text field (long)biography
Media field (single)avatarVia Advanced settings laten we Required field uitgeschakeld, zodat het niet verplicht is. Selecteer alléén Images bij allowed types.

Collection-type genaamd "Tag":

Veld typeNaamOpmerking
Text field (short)name
Text field (long)description
Text field (short)accentColorVul via Advanced settings > RegExp de pattern: ^#([A-Fa-f0-9]{6,8}|[A-Fa-f0-9]{3})$ voor HEX-kleur waarden.
Media field (single)iconSelecteer via Advanced settings alléén Images bij allowed types.
Relationblog_postsEen many-to-many relatie tussen Tag en Blog Post (na het aanmaken van de Blog post content-type).

Component-type genaamd "Blog post":

Veld typeNaamOpmerking
Text field (short)titleVia Advanced settings, selecteer Unique field.
Rich text (Markdown)content
Date field (datetime)publishedOnIedere content-type in Strapi heeft standaard een interne created en last updated veld. Voor deze blogpost-type, zullen we die negeren en onze eigen publishedOn en modifiedOn-velden gebruiken.
Date field (datetime)modifiedOnDit hoeft géén verplicht veld te zijn.
Text field (long)notesVia Advanced settings, selecteer Private field. Dit veld hoeft ook niet required te zijn, het is bedoeld voor de auteur om interne notities in te schrijven.
UID fieldslugAttached field: title.
ComponentseoMetadataKies voor existing -> SEO metadata -> Single.
Media (single)coverSelecteer via Advanced settings alléén Images bij allowed types.
RelationauthorRelatie naar één Author.
RelationtagsMany-to-many relatie tussen Blog post en Tags.

Aan content-types kun je tevens een icoontje toevoegen. Ook kun je via de geavanceerde eigenschappen extra opties instellen, zoals een min. en max. aantal karakters per veld.

Het is vrij intuïtief om content-types aan te maken. Mocht iets niet lukken, check dan de Creating content-types docs.

Strapi Content-Type Builder

Admingebruikers

Voor onze voorbeeldsite doen we alsof het door een kleine organisatie in gebruik wordt genomen. Deze organisatie heeft twee type medewerkers die content zullen bewerken, namelijk een editor en een editor-in-chief. Het is de bedoeling dat beide gebruikers blogposts mogen aanmaken en bewerken. Echter, alleen een editor-in-chief mag een blogpost publiceren of verwijderen en About us beheren. Beide admins krijgen toegang tot de admin panel.

Voordat we admin-gebruikers kunnen toevoegen met bepaalde rollen, moeten we eerst deze rollen definiëren. Ga via de admin panel naar Settings > Administration Panel > Roles. Hier zie je initieel drie rollen: Author, Editor en Super Admin. De gebruiker waarmee je op dit moment mee bent ingelogd is een Super Admin. Voor het gemak verwijderen we Author en Editor. Nu gaan we onze eigen organisatierollen toevoegen, te beginnen met Editor-In-Chief.

Klik op de "Add new role"-knop om dat te doen. Geef de naam en een beschrijving op. Onder het tabje "Collection Types", selecteer alles van Author, Blog-post en Tag. Onder het tabblad "Single Types", selecteer alles van About-us. Onder het tabje Plugins, ga naar Upload en schakel Access the Media Library, Create, Update, Download & Copy link in. Klik op opslaan.

Strapi admin role: Editor-In-Chief

Voeg nu nog een rol toe: Editor. Deze rol krijgt minder rechten: voor Author, selecteer alleen read en update. Voor Blog post, selecteer create, read en update. Voor Tag, selecteer read. Onder het tabje Plugins, ga naar Upload en schakel Access the Media Library, Create, Update, Download & Copy link in. Onder hetzelfde tabje, selecteer onder Content-manager de optie Configure view. Klik op opslaan.

Strapi admin role: Editor

Nu moeten we deze rollen toewijzen aan onze medewerkers Edson en Alex. Ga naar de pagina Settings > Administration Panel > Users. Hier vind je een overzicht van alle administrator-gebruikers. Laten we Edson en Alex uitnodigen: Klik op de knop "Invite new user". Per gebruiker die je toevoegt, krijg je een activatie-link. Via die link kan de desbetreffende persoon zijn account aanmaken.

Zodra de accounts zijn geactiveerd, zie je dat terug in dit overzicht.

Overzicht Strapi administrator-gebruikers

Edson & Alex kunnen dan beide zelf inloggen in de admin panel. Via hun panel kunnen zij data bewerken waar zij toegang tot hebben, op basis van hun rol.

Oké, dus nu hebben we een paar administrators met bepaalde rollen en relevante content-types. Nu kunnen onze medewerkers, Alex & Edson, content toevoegen! Tenminste, zodra ze toegang krijgen tot de CMS-applicatie — Het moet eerst nog ergens draaien.

Deployment van Strapi

Als je slechts de gratis community versie gebruikt, zoals ik, kun je Strapi alleen self-hosten. Gebruik je een betaalde versie, kun je eventueel gebruik maken van Strapi Cloud, check de Deployment docs wat het beste bij jou past.

In deze blogpost wil ik hier niet dieper op ingaan. Wellicht dat ik dat in een toekomstige post ga doen. In de volgende stappen blijf ik mijn lokale applicatie gebruiken.

Content toevoegen

In een real-life situatie, zouden Alex en Edson hun account hebben aangemaakt via de activatie-links. Voor deze blogpost heb ik dat zelf gedaan.

Laten we als Edson, onze editor-in-chief, inloggen en Authors, Tags en About-us invullen, voordat we Alex blogberichten laten schrijven.

Via de admin panel van Edsel, zie je in het linkermenu dat er al minder opties zijn dan de Super Admin. Ga naar Content Manager > About us. Hier zien we een formulier met de velden die we moeten invoeren. Bedenk iets leuks, vul het in, sla op en publish. Er zijn overigens twee descriptions, waarvan één voor de SEO-metadata. Het verschil is dat de ene op de pagina te zien is en de andere puur voor SEO. Dat soort logica zit in de front-end applicatie (Next.js) geïmplementeerd.

Mocht de layout van het formulier niet bevallen, dan kan een admin met de juiste bevoegdheden dit bewerken d.m.v. configure view. Per veld kun je ook de label-naam wijzigen (bijv. zodat je met een hoofdletter begint), en een beschrijving toevoegen.

Ga nu naar Tag en voeg een paar toe, bijvoorbeeld:

Strapi Tag-entries

Ten slotte voegen we twee Authors toe. Een author is het publieke profiel van de auteur van een blogpost die op de blogpost-pagina wordt getoond. Voeg er dus één voor Edson en één voor Alex toe.

Strapi Author-entries

Met de huidige data in het systeem, kunnen Alex en Edson nu blogberichten schrijven. Laten we nu inloggen als Alex en één blogpost schrijven.

Log in op de admin panel van Alex en ga naar de "Content Manager"-pagina. Je zult zien dat Alex, door de rollen die we toegewezen hebben, geen Authors en Tags kan aanmaken, maar wel een blogpost. Verzin iets leuks en sla het op.

Na het opslaan, zul je zien het dit een draft is en er geen optie is om het te publishen. Dat is omdat we hebben ingesteld dat alléén Editor-In-Chiefs dat kunnen doen. Log in als Edson en publish de blogpost. Als je wilt, kun je nog meer blogposts toevoegen, zodat we deze in het volgend hoofdstuk kunnen gebruiken.

Strapi Blog post-entries

Next.js front-end koppelen aan Strapi

Omdat voor deze blogpost de nadruk ligt op Strapi en niet op Next.js, heb ik alvast een simpele blogsite met Next.js & Tailwind CSS gebouwd en op GitHub geplaatst. Download of clone de repository voor het geval je ook wil experimenteren of de stappen volgt.

Front-end applicatie met mock-data

Start de Next.js applicatie met npm run dev. Navigeer naar de site in localhost via de browser. Hier zie je de blogsite met placeholder-data die we gaan vervangen met Strapi data. Er is een homepagina met blogposts, een blogpost pagina en een over ons-pagina.

Next.js blog site met mock data

Deze site is dusdanig gemaakt dat er een Typescript interface is voor het ophalen van data (server-side). In het bestand src/lib/data/data.ts vind je de volgende interface:

Typescript

1export interface Data {
2    getAboutUs: () => Promise<AboutUs>,
3    getAllTagsToFilterOn: () => Promise<Tag[]>,
4    getBlogPost: (slug: string) => Promise<BlogPost>,
5    getBlogPosts: (page: number, filterOnTags: string[]) 
6        => Promise<Page<BlogPostPreview>>,
7}

In src/lib/types.js vind je de Typescript type-definities. In src/lib/data/mockData.ts vind je de class MockData die de interface Data implementeert.

Data uit Strapi ophalen

Configuratie

Voordat we aan de slag gaan met de Strapi koppeling implementatie, wil ik eerst nog de configuratie toelichten. Er zijn twee configuratiebestanden: .env en .env.local. Die laatste bevat je geheime gegevens en moet je dus niet aan version control toevoegen.

In .env configureer je welke bron voor data de applicatie moet gebruiken. Bijvoorbeeld:

1# API-data instellen: "mock" | "strapi" | "strapi-tutorial"
2DATA_SOURCE="mock"
3
4STRAPI_BASE_URL="http://localhost:1337"
5STRAPI_API_BASE_URL="http://localhost:1337/api"

Voor de volgende stap, implementatie, vervang je mock met strapi-tutorial om je eigen implementatie te gebruiken. De overige Strapi eigenschappen hoef je niet te wijzigen, tenzij je de desbetreffende Strapi defaults hebt gewijzigd.

In .env.local plaats je één property, namelijk STRAPI_KEY met als waarde jouw Strapi API-key:

1STRAPI_KEY="XXXXXXXX"

Deze key kun je via jouw Strapi admin panel genereren en dient geheim te zijn. Vandaar dat het van belang is om alle Strapi API-calls server-side uit te voeren, anders kunnen end-users jouw API-key en endpoints inzien en misbruiken.

Implementatie

Om ervoor te zorgen dat we data vanuit Strapi ophalen om weer te geven, maken we dus een nieuwe Data implementatie. In src/lib/data/strapiDataTutorial.ts heb ik alvast een lege class toegevoegd. We moeten dus de methodes implementeren door Strapi-data via de API op te halen. Gebruik de REST API reference van Strapi voor hulp.

Wil je het zelf proberen? Implementeer dan alle methodes zelf. Kom je er niet uit, kun je verder lezen voor een voorbeelduitwerking van getBlogPosts: (pageSize: number, page: number, filterOnTags: string[]) => Promise<Page<BlogPostPreview>>.

Implementatie getBlogPosts

De method getBlogPosts wordt aangeroepen vanuit de homepagina. Deze functie haalt alle blogposts o.b.v. pagination & tag-filters op. Wat we eerst doen is een HTTP GET-request voorbereiden. De basis-URL is als volgt:

Typescript

1const url: URL = new URL(`${process.env.STRAPI_API_BASE_URL}/blog-posts`);

Dit haalt blogposts op met alle attributen met de standaard pagination (25 items per pagina). Bijvoorbeeld:

1// HTTP-GET http://localhost:1337/api/blog-posts
2// Resultaat:
3
4{
5  "data": [
6    {
7      "id": 7,
8      "attributes": {
9        "title": "...",
10        "content": "...",
11        "slug": "...",
12        "createdAt": "2024-05-08T09:51:33.440Z",
13        "updatedAt": "2024-05-08T09:51:33.440Z",
14        "publishedAt": "2024-05-15T10:17:15.674Z",
15        "modifiedOn": "2024-05-07T22:00:00.000Z",
16        "publishedOn": "2024-05-07T22:00:00.000Z"
17      }
18    },
19    ...
20  ],
21  "meta": {
22    "pagination": {
23      "page": 1,
24      "pageSize": 25,
25      "pageCount": 1,
26      "total": 7
27    }
28  }
29}

Hieruit kun je opmaken hoe zo'n resultaat eruitziet. Let vooral op de attributes-eigenschap. Hierin zie je de fields van de content-type terug. Zoals je misschien al hebt opgemerkt, missen er een aantal, namelijk relation en media fields (cover, tags, author en seoMetadata). Daarnaast hebben we voor de homepagina ook niet alle velden nodig, zoals content. Deze wordt namelijk toch niet weergegeven en is dus zonde van de resources.

Dus, we moeten o.b.v. van deze GET-request een aantal dingen aanpassen, namelijk:

  1. Specificeren welke velden we willen
  2. Velden van relaties verkrijgen
  3. Pagination instellen
  4. Filteren o.b.v. tags
  5. Filteren o.b.v. onze eigen publishedOn datum
  6. Sorteren op publishedOn datum van nieuw naar oud

Dit alles doen we met behulp van URL-query parameters, vandaar dit ik in mijn implementatie werk met het Javascript URL-object.

Specificeren welke velden we willen: Dit doen we met field selection. Per niet-relatie field, voegen we een query parameter toe in de vorm field[0]=title. Waarbij 0 een volgnummer tussen de fields is en title de naam van de field. In ons geval doen we dus het volgende:

JavaScript

1url.searchParams.set("fields[0]", "title");
2url.searchParams.set("fields[1]", "publishedOn");
3url.searchParams.set("fields[2]", "modifiedOn");
4url.searchParams.set("fields[3]", "slug");

Velden van relatie verkrijgen: Dit doen we met de populate parameter. Per relatie geven we aan welke fields we daarvan willen krijgen. Er zijn verschillende manieren om dat te doen, dus check vooral de docs. In ons geval doen we dat als volgt:

JavaScript

1// Cover relation (media type):
2url.searchParams.set("populate[cover][fields][0]", "url");
3
4// Author relation:
5url.searchParams.set("populate[author][fields][0]", "name");
6url.searchParams.set("populate[author][populate][avatar][fields][0]",
7    "url");
8
9// Tags relation:
10url.searchParams.set("populate[tags][fields][0]", "name");
11url.searchParams.set("populate[tags][fields][1]", "accentColor");
12url.searchParams.set("populate[tags][fields][2]", "description");
13url.searchParams.set("populate[tags][populate][icon][fields][0]",
14    "url");

Op regel 5 zie je bijvoorbeeld dat we van de Author-relatie het veld name willen. Aangezien we ook de avatar (media type) van de Author willen hebben moeten we een relatie van een relatie krijgen, dat zie je op regel 6.

Pagination instellen: In ons geval maken we gebruik van pagination by page. We moeten de grootte van iedere pagina en een huidig paginanummer specificeren. Let op: de paginanummers beginnen bij 1 en niet bij 0. We doen dit als volgt:

JavaScript

1url.searchParams.set("pagination[page]", page + "");
2url.searchParams.set("pagination[pageSize]", pageSize + "");
3url.searchParams.set("pagination[withCount]", "true");

Met pagination[withCount] = true, geven we aan dat we in de response ook de pagination-eigenschappen willen. De default-waarde is overigens al true.

Filteren o.b.v. tags: Zoals je kunt zien aan de methode-definitie van getBlogPosts, is er een parameter filterOnTags: string[]. Deze parameter geeft aan voor welke tags we de blogposts willen. Als deze array leeg is, willen we alle blog posts. En als er meerdere filterOnTags zijn, dan moeten onze matches minimaal één van de tags hebben. De string waarde is de field name van content-type Tag zonder hoofdletters. Zie de docs voor alle Strapi filter opties. In ons geval zie het er als volgt uit:

Typescript

1if (filterOnTags.length > 0) {
2    filterOnTags.forEach((f: string, i: number) => {
3        url.searchParams
4            .set(`filters[$or][${i}][tags][name][$eqi]`, f);
5    });
6}

Voor iedere string in filterOnTags voegen we een filter query toe. Let op de $or-statement, deze moeten we gebruiken omdat we meerdere filterOnTags kunnen hebben, en maar aan één hoeven te matchen.

Filteren o.b.v. onze eigen publishedOn datum: Ook hier gebruiken we een filter-query voor. In dit geval willen we dat publishedOn vóór de huidige datum is. Hiervoor gebruiken we de $lte-operator:

JavaScript

1url.searchParams.set(
2    "filters[publishedOn][$lte]",
3    new Date().toISOString());

Sorteren op publishedOn datum van nieuw naar oud: Ten slotte willen we de resultaten sorteren van nieuw naar oud. Dit doen we met sorting:

JavaScript

1url.searchParams.set("sort[0]", "publishedOn:desc");

Onze URL is nu klaar. We gebruiken fetch() om de GET-request uit te voeren. Voordat dat kan, moeten we nog een Authorization-header toevoegen:

Typescript

1const response = await fetch(url.toString(), {
2    headers: {
3        "Authorization": `Bearer ${process.env.STRAPI_KEY}`,
4    }
5});
6const jsonResponse: Page<StrapiBlogPostPreview> = await response.json();

Op regel 6 verkrijgen we het JSON-resultaat. Zelf vind ik het fijn om met Typescript-types te werken, dus die heb ik ook gedefinieerd. Dat zal helpen met het transformeren van de datastructuur Page<StrapiBlogPostPreview> naar Page<BlogPostPreview>. Analyseer beide types en probeer een converter te schrijven en het resultaat daarvan te retourneren.

Het eindresultaat van getBlogPosts zal dan zijn:

Typescript

1async getBlogPosts(
2    pageSize: number, 
3    page: number, 
4    filterOnTags: string[]): Promise<Page<BlogPostPreview>> {
5    const url: URL = new URL(`${process.env.STRAPI_API_BASE_URL}/blog-posts`);
6
7    // Blog post properties
8    url.searchParams.set("fields[0]", "title");
9    url.searchParams.set("fields[1]", "publishedOn");
10    url.searchParams.set("fields[2]", "modifiedOn");
11    url.searchParams.set("fields[3]", "slug");
12
13    // Cover relation (media type):
14    url.searchParams.set("populate[cover][fields][0]", "url");
15
16    // Author relation:
17    url.searchParams.set("populate[author][fields][0]", "name");
18    url.searchParams.set("populate[author][populate][avatar][fields][0]",
19        "url");
20
21    // Tags relation:
22    url.searchParams.set("populate[tags][fields][0]", "name");
23    url.searchParams.set("populate[tags][fields][1]", "accentColor");
24    url.searchParams.set("populate[tags][fields][2]", "description");
25    url.searchParams.set("populate[tags][populate][icon][fields][0]", "url");
26
27    // Filter publishedOn
28    url.searchParams.set("filters[publishedOn][$lte]", new Date().toISOString());
29
30    // Pagination
31    url.searchParams.set("pagination[page]", page + "");
32    url.searchParams.set("pagination[pageSize]", pageSize + "");
33    url.searchParams.set("pagination[withCount]", "true");
34
35    // Sorting
36    url.searchParams.set("sort[0]", "publishedOn:desc");
37
38    // Filter on tags
39    if (filterOnTags.length > 0) {
40        filterOnTags.forEach((f: string, i: number) => {
41            url.searchParams.set(`filters[$or][${i}][tags][name][$eqi]`, f);
42        });
43    }
44
45    // Make request
46    const response = await fetch(url.toString(), this.getHeaders());
47    const jsonResponse: Page<StrapiBlogPostPreview> = await response.json();
48
49    // Transform `Page<StrapiBlogPostPreview>` to `Page<BlogPostPreview>`
50    return {
51        data: jsonResponse.data.map(this.convertBlogPostProps),
52        meta: jsonResponse.meta,
53    };
54};
55
56private convertBlogPostProps(
57    post: StrapiBlogPostPreview | StrapiBlogPost): BlogPostPreview | BlogPost {
58    return {
59        ...post.attributes,
60        cover: process.env.STRAPI_BASE_URL + post.attributes.cover.data.attributes.url,
61        tags: post.attributes.tags.data.map((tag: StrapiTag) => ({
62            ...tag.attributes,
63            icon: process.env.STRAPI_BASE_URL + tag.attributes.icon.data.attributes.url,
64        })),
65        author: {
66            ...post.attributes.author.data.attributes,
67            avatar: process.env.STRAPI_BASE_URL +
68                post.attributes.author.data.attributes.avatar.data.attributes.url,
69        },
70        formattedPublishedOn: formatDistance(new Date(post.attributes.publishedOn), new Date(), {
71            locale: nl,
72            addSuffix: true
73        }),
74    }
75}
76
77private getHeaders() {
78    return {
79        headers: {
80            "Authorization": `Bearer ${process.env.STRAPI_KEY}`,
81        },
82    };
83}

Voor formattedPublishedOn, maak ik gebruik van date-fns om publishedOn te formatten.

Probeer de overige methodes zelf te implementeren. Lukt het niet, of wil je direct een implementatie zien? Check dan mijn uitwerking in src/lib/data/strapiData.ts.

Zodra je eenmaal door hebt hoe de Strapi API werkt, zul je merken dat het vrij simpel is om data vanuit Strapi in een Next.js front-end applicatie te integreren. Of wat voor front-end dan ook.

Tip: Als je de Strapi API-calls los wilt testen, kun je dat doen met API-test applicaties, zoals Postman of Insomnia. Daarnaast heeft Strapi een handige interactieve query builder.

Conclusie

Strapi een open-source content-management systeem. Het stelt je in staat om relatief snel een backend systeem op te zetten en in te richten. Je kunt Strapi gebruiken voor beheren van data voor jouw bedrijfswebsite, blog, dynamische app, en nog veel meer.

De website die we in deze blogpost hebben geïmplementeerd, gebruikt slechts de basisonderdelen van Strapi. Er zijn nog veel meer onderdelen die niet aan bod zijn gekomen. Denk bijvoorbeeld aan i18n, security, hosting & CI/CD, CORS en externe plugins.

Mocht je nog verder willen experimenteren, dan zijn de Strapi docs jouw vriend!

Delen

Ervaren software engineer nodig?

Ik help bedrijven met softwareoplossingen (SaaS, automatiseren en meer) — van backend tot frontend. Neem vrijblijvend contact op om te kijken hoe ik kan bijdragen aan jouw project.

  • Robuuste backend: Java/Kotlin, SQL, Spring Framework
  • Gebruiksvriendelijke front-end: Next.js, React, Typescript, ES6
  • Snelle doorontwikkeling: Continuous integration & deployment, Jenkins
  • Efficiënte samenwerking: Agile, Scrum, Jira, Git, Bitbucket, GitHub
  • Freelance software-ontwikkelaar uit Arnhem