Strapi CMS Integration for Content Management
Strapi — open-source headless CMS on Node.js. Provides admin panel for content management and auto-generates REST and GraphQL APIs by created content types. Deploys on own server — data stays under control.
Architecture
Strapi consists of two parts: Admin Panel — React app for managing content, schemas, settings; Backend — Node.js/Koa server with auto-generated API routes.
Headless workflow:
Strapi Admin → creates content
Strapi API → serves content to clients
Frontend → Next.js / Nuxt / mobile app
Installation
npx create-strapi-app@latest my-cms --quickstart
# or with database
npx create-strapi-app@latest my-cms \
--dbclient=postgres \
--dbhost=127.0.0.1 \
--dbport=5432 \
--dbname=strapi \
--dbusername=strapi \
--dbpassword=secret
Supported databases: SQLite (development), PostgreSQL, MySQL/MariaDB.
Content Types
Create via Content-Type Builder in admin (drag-and-drop) or code:
src/api/article/content-types/article/schema.json
{
"kind": "collectionType",
"collectionName": "articles",
"info": {
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"attributes": {
"title": { "type": "string", "required": true },
"slug": { "type": "uid", "targetField": "title" },
"content": { "type": "richtext" },
"cover": { "type": "media", "allowedTypes": ["images"] },
"author": { "type": "relation", "relation": "manyToOne", "target": "plugin::users-permissions.user" },
"publishedAt": { "type": "datetime" }
}
}
Schema changes auto-create migrations on next startup.
API Requests
# Get all articles
GET /api/articles
# With populate for relations
GET /api/articles?populate=cover,author
# Filtering
GET /api/articles?filters[title][$containsi]=strapi&filters[publishedAt][$notNull]=true
# Sorting and pagination
GET /api/articles?sort=publishedAt:desc&pagination[page]=1&pagination[pageSize]=10
Response:
{
"data": [
{
"id": 1,
"attributes": {
"title": "Getting started with Strapi",
"slug": "getting-started",
"content": "<p>...</p>",
"publishedAt": "2025-03-01T10:00:00.000Z",
"cover": { "data": { "attributes": { "url": "/uploads/cover.jpg" } } }
}
}
],
"meta": { "pagination": { "page": 1, "pageSize": 10, "total": 42 } }
}
Custom Routes and Controllers
// src/api/article/routes/custom-article.js
module.exports = {
routes: [
{
method: 'GET',
path: '/articles/featured',
handler: 'article.featured',
},
],
};
// src/api/article/controllers/article.js
module.exports = {
async featured(ctx) {
const articles = await strapi.entityService.findMany('api::article.article', {
filters: { featured: true, publishedAt: { $notNull: true } },
sort: { publishedAt: 'desc' },
limit: 6,
populate: ['cover'],
});
return { data: articles };
},
};
Lifecycle Hooks
// src/api/article/content-types/article/lifecycles.js
module.exports = {
async beforeCreate(event) {
const { data } = event.params;
if (!data.slug) {
data.slug = slugify(data.title);
}
},
async afterCreate(event) {
const { result } = event;
// notify channel, clear cache, index in Algolia
await notifyChannel('new-article', result.id);
},
};
Internationalization
Built-in i18n plugin:
# Enable in admin: Settings → Internationalization → Add locale
In schema, enable pluginOptions: { i18n: { localized: true } } for fields. API: GET /api/articles?locale=uk.
Production Deployment
# docker-compose.yml
services:
strapi:
image: node:20-alpine
command: yarn start
environment:
NODE_ENV: production
DATABASE_URL: postgres://user:pass@db:5432/strapi
JWT_SECRET: ${JWT_SECRET}
db:
image: postgres:16
For production, move media to S3 via @strapi/provider-upload-aws-s3.
Timeline
Setup, content types, REST API, permissions, deploy — 3–5 days. With custom plugins, i18n, webhooks, monitoring — 1–2 weeks.







