Kas ta headless architektūra ir kodėl ji tapo tokia populiari
Prisimenu laikus, kai kūrėme svetaines su WordPress ar Drupal, ir viskas buvo sulipdyta į vieną gabalą – tiek backend, tiek frontend. Veikė, žinoma, bet kai reikėdavo tą patį turinį rodyti mobilioje aplikacijoje ar dar kur nors, prasidėdavo tikras galvos skausmas. Headless CMS atsirado kaip atsakas į šią problemą, ir dabar jau sunku įsivaizduoti modernų projektą be šios architektūros.
Esmė paprasta: atskiri turinį nuo jo pateikimo. Backend rūpinasi tik duomenimis ir jų valdymu, o kaip tas turinys bus rodomas – tai jau visiškai kitas klausimas. Per API gauni duomenis ir darai su jais ką nori. Nori React aplikaciją? Prašom. Flutter mobilią? Jokių problemų. Net IoT įrenginiams gali turinį siųsti, jei reikia.
Strapi čia išsiskiria tuo, kad jis open-source, pakankamai lankstus ir turi tikrai gerą developer experience. Nereikia pradėti nuo nulio – gauni paruoštą admin panelę, autentifikaciją, teisių valdymą. Bet kartu nesi užrakintas – gali keisti beveik viską.
Strapi įdiegimas ir pradinė konfigūracija
Pradėti su Strapi tikrai nesudėtinga, bet yra keletas niuansų, kuriuos verta žinoti iš karto. Pirmiausia, reikia normalios Node.js versijos – rekomenduoju naudoti LTS (dabar tai būtų 18.x ar 20.x). Su senesnėmis versijomis gali veikti, bet kam rizikuoti?
Naują projektą sukurti galima viena komanda:
npx create-strapi-app@latest mano-projektas --quickstart
Flag’as --quickstart automatiškai naudoja SQLite duomenų bazę, kas puiku developmentui. Bet production’e tikrai rekomenduoju PostgreSQL arba MySQL. SQLite tiesiog nėra skirtas rimtam darbui su daug concurrent connections.
Kai projektas sukurtas, pirmas dalykas – sukonfigūruoti aplinkos kintamuosius. Strapi naudoja .env failą, ir čia svarbu neužmiršti kelių dalykų:
APP_KEYS– šie raktai naudojami session encryption’ui, generuojami automatiškai, bet production’e būtinai turi būti unikalūsAPI_TOKEN_SALT– svarbu API token’ų saugumuiADMIN_JWT_SECRET– admin panelės JWT autentifikacijaiJWT_SECRET– API JWT autentifikacijai
Vienas iš dažniausių klaidų, kurias matau – žmonės palieka default reikšmes production’e. Tai saugumo katastrofa. Naudokite stiprius, atsitiktinius string’us.
Content Types kūrimas ir ryšių valdymas
Čia prasideda tikrasis darbas. Strapi turi puikų Content-Type Builder’į, kuris leidžia sukurti duomenų struktūras per UI. Bet kai projektas auga, greičiau ir patogiau tai daryti per kodą.
Pavyzdžiui, jei kuriate blog’ą, jums reikės Article ir Author content type’ų. Strapi juos saugo src/api/ direktorijoje. Kiekvienas content type turi schema failą, kur aprašote laukus ir jų tipus:
{
"kind": "collectionType",
"collectionName": "articles",
"info": {
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"content": {
"type": "richtext"
},
"author": {
"type": "relation",
"relation": "manyToOne",
"target": "api::author.author",
"inversedBy": "articles"
}
}
}
Ryšiai tarp content type’ų – tai vieta, kur dažnai kyla klausimų. Strapi palaiko visus standartinius ryšių tipus: oneToOne, oneToMany, manyToOne, manyToMany. Svarbu suprasti, kad kai sukuriate ryšį, reikia pagalvoti apie abi puses – jei Article turi Author, tai Author turėtų turėti inversedBy nuorodą atgal į articles.
Praktiškas patarimas: nenaudokite per daug nested relations. Jei turite Article -> Author -> Company -> Country, ir bandote viską fetch’inti vienu query, performance nukentės. Geriau darykite kelis atskirus request’us arba naudokite populate strategiškai.
API customizacija ir lifecycle hooks
Strapi automatiškai generuoja REST ir GraphQL endpoints visiem content type’ams, bet realybėje beveik visada reikia kažką customizuoti. Gal reikia papildomos validacijos, gal norite modifikuoti duomenis prieš išsaugant, gal reikia integracijos su trečiųjų šalių servisais.
Lifecycle hooks – tai vieta, kur dažniausiai vyksta tokia logika. Kiekvienas content type gali turėti savo lifecycle failą src/api/[content-type]/content-types/[content-type]/lifecycles.js:
module.exports = {
async beforeCreate(event) {
const { data } = event.params;
// Pavyzdžiui, automatiškai generuojame slug iš title
if (data.title && !data.slug) {
data.slug = data.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
},
async afterCreate(event) {
const { result } = event;
// Galime siųsti notification, invalidate cache, ir t.t.
console.log('Naujas įrašas sukurtas:', result.id);
}
};
Yra keletas hook’ų, kuriuos galite naudoti: beforeCreate, afterCreate, beforeUpdate, afterUpdate, beforeDelete, afterDelete. Kiekvienas gauna event objektą su visa reikalinga informacija.
Kai reikia dar daugiau kontrolės, galite override’inti visus controller’ius ar service’us. Pavyzdžiui, jei norite custom find logikos:
// src/api/article/controllers/article.js
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::article.article', ({ strapi }) => ({
async find(ctx) {
// Custom logika prieš standartinį find
const { query } = ctx;
// Galime pridėti papildomus filtrus
query.filters = {
...query.filters,
publishedAt: { $notNull: true }
};
const { data, meta } = await super.find(ctx);
// Galime modifikuoti response
return { data, meta };
}
}));
Autentifikacija ir teisių valdymas
Strapi turi built-in autentifikacijos sistemą, kuri veikia gerai, bet reikia suprasti jos niuansus. Yra du atskiri autentifikacijos kontekstai: admin panelė ir API users. Tai visiškai atskiros sistemos su skirtingais JWT token’ais.
API users valdymui Strapi naudoja Users & Permissions plugin’ą. Čia galite sukurti roles (Authenticated, Public, ir custom), ir kiekvienai role priskirti permissions konkretiems endpoints. Bet default setup’as dažnai per liberalus production’ui.
Štai ką rekomenduoju padaryti iš karto:
- Public role – leisti tik read operacijas tam turiniui, kuris tikrai public
- Authenticated role – leisti tik tai, ką user’is tikrai turi teisę daryti
- Sukurti custom roles specifiniams use case’ams (Editor, Moderator, ir pan.)
Dažna klaida – palikti visus endpoints public, nes „taip lengviau developinti”. Paskui užmiršti pakeisti production’e. Geriau iš karto konfigūruoti teisingai.
Kai reikia labai specifinės teisių logikos (pvz., user gali redaguoti tik savo įrašus), naudokite policies. Sukuriate policy failą:
// src/policies/is-owner.js
module.exports = async (policyContext, config, { strapi }) => {
const { id } = policyContext.params;
const userId = policyContext.state.user.id;
const entity = await strapi.entityService.findOne(
'api::article.article',
id
);
if (entity.author.id !== userId) {
return false;
}
return true;
};
Ir priskirkite jį route’ui:
// src/api/article/routes/article.js
module.exports = {
routes: [
{
method: 'PUT',
path: '/articles/:id',
handler: 'article.update',
config: {
policies: ['is-owner']
}
}
]
};
Media biblioteka ir failų valdymas
Strapi media library yra viena iš stipriausių jo pusių, bet čia irgi yra savo specifika. Default’u failai saugomi local file system’e, kas development’ui puiku, bet production’e tikrai norite naudoti cloud storage.
Populiariausi provider’iai: AWS S3, Cloudinary, DigitalOcean Spaces. Konfigūracija gana paprasta, reikia įdiegti atitinkamą plugin’ą ir sukonfigūruoti credentials:
npm install @strapi/provider-upload-aws-s3
Tada config/plugins.js:
module.exports = ({ env }) => ({
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_ACCESS_SECRET'),
region: env('AWS_REGION'),
params: {
Bucket: env('AWS_BUCKET'),
},
},
},
},
});
Vienas dalykas, kurį dažnai užmiršta – image optimization. Strapi automatiškai generuoja kelis image format’us (thumbnail, small, medium, large), bet jei reikia daugiau kontrolės, galite naudoti sharp library ir custom middleware.
Praktinis patarimas: jei turite daug images, įjunkite lazy loading frontend’e ir naudokite responsive images su srcset. Strapi jums duoda visus reikalingus format’us, tereikia jais pasinaudoti.
Performance optimization ir caching strategijos
Kai projektas pradeda augti, performance tampa kritiška. Strapi pats savaime nėra lėtas, bet jei nekonfigūruojate teisingai, gali tapti. Keletas dalykų, į kuriuos būtina atkreipti dėmesį:
Database queries optimization. Strapi naudoja Knex.js po kapotas, ir jei neatsargiai naudojate populate, galite lengvai gauti N+1 query problemą. Visada naudokite populate strategiškai:
// Blogai
const articles = await strapi.entityService.findMany('api::article.article', {
populate: '*' // Fetch'ina VISKĄ, įskaitant nested relations
});
// Gerai
const articles = await strapi.entityService.findMany('api::article.article', {
populate: {
author: {
fields: ['name', 'email']
},
coverImage: {
fields: ['url', 'formats']
}
}
});
Response caching. Strapi neturi built-in HTTP cache, bet galite lengvai pridėti. Rekomenduoju naudoti Redis su kažkuo kaip strapi-plugin-rest-cache arba implementuoti custom middleware su node-cache.
Paprastas caching middleware pavyzdys:
// config/middlewares.js
module.exports = [
'strapi::errors',
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': ["'self'", 'data:', 'blob:', 'https://your-cdn.com'],
'media-src': ["'self'", 'data:', 'blob:', 'https://your-cdn.com'],
upgradeInsecureRequests: null,
},
},
},
},
'strapi::cors',
'strapi::poweredBy',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
];
Database indexing. Jei darote daug queries pagal tam tikrus laukus, pridėkite index’us. Strapi leidžia tai daryti schema definition’e:
"slug": {
"type": "string",
"unique": true,
"index": true
}
Deployment ir production best practices
Kai ateina laikas deploy’inti į production, yra keletas dalykų, kuriuos būtina padaryti teisingai. Pirmiausia – aplinkos kintamieji. Niekada, NIEKADA necommit’inkite .env failo su production credentials. Naudokite environment variables per jūsų hosting platformą.
Database migracijų valdymas – tai kitas svarbus aspektas. Strapi automatiškai susinchronizuoja schema su database development’e, bet production’e rekomenduoju išjungti auto-migration ir valdyti migraciją rankiniu būdu:
// config/database.js
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
host: env('DATABASE_HOST'),
port: env.int('DATABASE_PORT'),
database: env('DATABASE_NAME'),
user: env('DATABASE_USERNAME'),
password: env('DATABASE_PASSWORD'),
ssl: env.bool('DATABASE_SSL', false) && {
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true)
}
},
pool: {
min: 2,
max: 10
}
}
});
Monitoring ir logging – būtinybė production’e. Integruokite Strapi su Sentry error tracking’ui, naudokite Winston ar Pino logging’ui, setup’inkite health check endpoint’us.
Paprastas health check galite padaryti custom route’u:
// src/api/health/routes/health.js
module.exports = {
routes: [
{
method: 'GET',
path: '/health',
handler: 'health.check',
config: {
auth: false
}
}
]
};
// src/api/health/controllers/health.js
module.exports = {
async check(ctx) {
try {
// Patikriname database connection
await strapi.db.connection.raw('SELECT 1');
ctx.body = {
status: 'ok',
timestamp: new Date().toISOString()
};
} catch (error) {
ctx.status = 503;
ctx.body = {
status: 'error',
message: error.message
};
}
}
};
Kai viskas susiglaudžia į vieną paveikslą
Dirbant su Strapi jau keletą metų, galiu pasakyti, kad tai tikrai solid pasirinkimas headless CMS projektams. Taip, yra dalykų, kurie galėtų būti geresni – dokumentacija kartais atsilieka nuo kodo, kai kurie plugin’ai nebepalaikomi, performance tuning’as reikalauja pastangų. Bet bendras developer experience yra tikrai geras.
Svarbiausias dalykas – neskubėti ir iš karto setup’inti projektą teisingai. Užtrunka papildomą dieną-dvi sukonfigūruoti proper authentication, caching, monitoring, bet vėliau sutaupysite savaičių ar net mėnesių. Matau daug projektų, kur žmonės pradeda su quickstart setup’u ir tiesiog tęsia su tuo į production. Paskui stebisi, kodėl viskas lėta ir nestabilu.
Dar vienas patarimas – sekite Strapi community ir GitHub issues. Framework’as aktyviai vystomas, kas mėnesį išeina nauji release’ai. Kartais breaking changes, kartais naujos features. Verta būti kurse, kas vyksta.
Ir galiausiai – Strapi nėra silver bullet. Jei jūsų projektas labai specifinis, su sudėtinga business logika, gal geriau būtų custom backend. Bet jei reikia content management sistemos, kuri lanksti, plečiama ir turi gerą DX – Strapi tikrai verta rimto dėmesio. Tiesiog padarykite namų darbus, suprasite architektūrą, ir turėsite solid foundation savo projektui.

