🎯 empfohlene Sammlungen
Balanced sample collections from various categories for you to explore
Strapi Headless CMS Beispiele
Umfassende Strapi Beispiele mit Content Modeling, API Integration, Authentifizierung, Plugins und Deployment Patterns
💻 Strapi Content Modeling javascript
🟡 intermediate
⭐⭐⭐
Vollständige Content-Type-Definitionen, Beziehungen und Feldkonfigurationen für verschiedene Anwendungsfälle
⏱️ 35 min
🏷️ strapi, content modeling, cms
Prerequisites:
Strapi basics, Content modeling concepts, JSON schema, Database concepts
// Strapi Content Modeling Examples
// File: config/api/article/content-types/article/schema.json
{
"kind": "collectionType",
"collectionName": "articles",
"info": {
"singularName": "article",
"pluralName": "articles",
"displayName": "Article",
"description": "Blog articles with rich content and media"
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {
"i18n": {
"localized": true
}
},
"attributes": {
"title": {
"type": "string",
"required": true,
"maxLength": 255,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"slug": {
"type": "uid",
"targetField": "title",
"required": true
},
"excerpt": {
"type": "text",
"maxLength": 500,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"content": {
"type": "richtext",
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"featured_image": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images"]
},
"gallery": {
"type": "media",
"multiple": true,
"required": false,
"allowedTypes": ["images"]
},
"category": {
"type": "relation",
"relation": "manyToOne",
"target": "api::category.category"
},
"tags": {
"type": "relation",
"relation": "manyToMany",
"target": "api::tag.tag"
},
"author": {
"type": "relation",
"relation": "manyToOne",
"target": "api::author.author"
},
"seo": {
"type": "component",
"repeatable": false,
"component": "shared.seo"
},
"published_at": {
"type": "datetime",
"configurable": false,
"writable": false,
"default": "now"
},
"reading_time": {
"type": "integer",
"min": 0
},
"view_count": {
"type": "integer",
"default": 0,
"private": true
}
}
}
// File: config/api/category/content-types/category/schema.json
{
"kind": "collectionType",
"collectionName": "categories",
"info": {
"singularName": "category",
"pluralName": "categories",
"displayName": "Category",
"description": "Article categories for content organization"
},
"options": {
"draftAndPublish": false
},
"pluginOptions": {
"i18n": {
"localized": true
}
},
"attributes": {
"name": {
"type": "string",
"required": true,
"maxLength": 100,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"slug": {
"type": "uid",
"targetField": "name",
"required": true
},
"description": {
"type": "text",
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"color": {
"type": "string",
"regex": "^#[0-9A-Fa-f]{6}$"
},
"icon": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images"]
},
"parent": {
"type": "relation",
"relation": "manyToOne",
"target": "api::category.category",
"nullable": true
},
"children": {
"type": "relation",
"relation": "oneToMany",
"target": "api::category.category",
"mappedBy": "parent",
"private": true
},
"articles_count": {
"type": "integer",
"default": 0,
"private": true
}
}
}
// File: config/api/product/content-types/product/schema.json
{
"kind": "collectionType",
"collectionName": "products",
"info": {
"singularName": "product",
"pluralName": "products",
"displayName": "Product",
"description": "E-commerce products with variants and pricing"
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {
"i18n": {
"localized": true
}
},
"attributes": {
"name": {
"type": "string",
"required": true,
"maxLength": 255,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"slug": {
"type": "uid",
"targetField": "name",
"required": true
},
"description": {
"type": "richtext",
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"short_description": {
"type": "text",
"maxLength": 500,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"sku": {
"type": "string",
"required": true,
"unique": true,
"regex": "^[A-Z0-9-]+$"
},
"price": {
"type": "decimal",
"required": true,
"min": 0
},
"compare_price": {
"type": "decimal",
"min": 0
},
"cost": {
"type": "decimal",
"min": 0,
"private": true
},
"images": {
"type": "media",
"multiple": true,
"required": false,
"allowedTypes": ["images"]
},
"variants": {
"type": "component",
"repeatable": true,
"component": "product.product-variant"
},
"inventory": {
"type": "component",
"repeatable": false,
"component": "product.inventory"
},
"shipping": {
"type": "component",
"repeatable": false,
"component": "product.shipping"
},
"seo": {
"type": "component",
"repeatable": false,
"component": "shared.seo"
},
"categories": {
"type": "relation",
"relation": "manyToMany",
"target": "api::product-category.product-category"
},
"brand": {
"type": "relation",
"relation": "manyToOne",
"target": "api::brand.brand"
},
"tags": {
"type": "relation",
"relation": "manyToMany",
"target": "api::tag.tag"
},
"attributes": {
"type": "relation",
"relation": "manyToMany",
"target": "api::product-attribute.product-attribute"
},
"reviews": {
"type": "relation",
"relation": "oneToMany",
"target": "api::review.review",
"mappedBy": "product"
},
"featured": {
"type": "boolean",
"default": false
},
"available": {
"type": "boolean",
"default": true
}
}
}
// File: config/components/product/product-variant.json
{
"collectionName": "components_product_variants",
"info": {
"displayName": "Product Variant",
"icon": "cube",
"description": "Product variants with different options and pricing"
},
"options": {},
"attributes": {
"name": {
"type": "string",
"required": true,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"sku": {
"type": "string",
"required": true,
"unique": true
},
"price": {
"type": "decimal",
"required": true
},
"compare_price": {
"type": "decimal"
},
"weight": {
"type": "decimal"
},
"inventory": {
"type": "integer",
"default": 0
},
"options": {
"type": "json",
"default": {}
},
"image": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images"]
}
}
}
// File: config/components/shared/seo.json
{
"collectionName": "components_shared_seos",
"info": {
"displayName": "SEO",
"icon": "search",
"description": "SEO metadata for search engine optimization"
},
"options": {},
"pluginOptions": {
"i18n": {
"localized": true
}
},
"attributes": {
"meta_title": {
"type": "string",
"maxLength": 60,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"meta_description": {
"type": "text",
"maxLength": 160,
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"keywords": {
"type": "text"
},
"canonical_url": {
"type": "string"
},
"og_image": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images"]
},
"structured_data": {
"type": "json"
},
"meta_robots": {
"type": "enumeration",
"enum": ["index", "noindex", "follow", "nofollow"],
"default": "index,follow"
}
}
}
// File: database/seeds/01-categories.js
module.exports = {
async up(strapi) {
// Create main categories
const categories = [
{
name: 'Technology',
slug: 'technology',
description: 'Articles about technology and programming',
color: '#3B82F6'
},
{
name: 'Design',
slug: 'design',
description: 'Design articles and tutorials',
color: '#8B5CF6'
},
{
name: 'Business',
slug: 'business',
description: 'Business and entrepreneurship articles',
color: '#10B981'
},
{
name: 'Marketing',
slug: 'marketing',
description: 'Marketing strategies and tips',
color: '#F59E0B'
}
];
for (const category of categories) {
await strapi.query('api::category.category').create({
data: category
});
}
},
async down(strapi) {
// Remove seeded categories
await strapi.query('api::category.category').deleteMany({
where: {
slug: ['technology', 'design', 'business', 'marketing']
}
});
}
};
// File: database/seeds/02-tags.js
module.exports = {
async up(strapi) {
const tags = [
{ name: 'JavaScript', slug: 'javascript' },
{ name: 'React', slug: 'react' },
{ name: 'Node.js', slug: 'nodejs' },
{ name: 'CSS', slug: 'css' },
{ name: 'HTML', slug: 'html' },
{ name: 'TypeScript', slug: 'typescript' },
{ name: 'Vue.js', slug: 'vuejs' },
{ name: 'Angular', slug: 'angular' },
{ name: 'Python', slug: 'python' },
{ name: 'Machine Learning', slug: 'machine-learning' },
{ name: 'UI/UX', slug: 'ui-ux' },
{ name: 'Startup', slug: 'startup' },
{ name: 'SEO', slug: 'seo' },
{ name: 'Analytics', slug: 'analytics' },
{ name: 'E-commerce', slug: 'ecommerce' }
];
for (const tag of tags) {
await strapi.query('api::tag.tag').create({
data: tag
});
}
},
async down(strapi) {
const slugs = tags.map(tag => tag.slug);
await strapi.query('api::tag.tag').deleteMany({
where: {
slug: slugs
}
});
}
};
// File: database/seeds/03-authors.js
module.exports = {
async up(strapi) {
const authors = [
{
name: 'John Doe',
email: '[email protected]',
bio: 'Full-stack developer with 10+ years of experience',
avatar: null,
social: {
twitter: 'johndoe',
github: 'johndoe',
linkedin: 'john-doe'
}
},
{
name: 'Jane Smith',
email: '[email protected]',
bio: 'UX designer passionate about user experience',
avatar: null,
social: {
twitter: 'janesmith',
dribbble: 'janesmith',
behance: 'jane-smith'
}
}
];
for (const author of authors) {
await strapi.query('api::author.author').create({
data: author
});
}
},
async down(strapi) {
await strapi.query('api::author.author').deleteMany();
}
};
💻 Strapi API Integration javascript
🔴 complex
⭐⭐⭐⭐
Vollständige API-Integrationsbeispiele mit REST und GraphQL Abfragen, Authentifizierung und Frontend-Implementierung
⏱️ 40 min
🏷️ strapi, api, integration, frontend
Prerequisites:
Strapi basics, REST/GraphQL APIs, React hooks, JavaScript ES6+, Authentication concepts
// Strapi API Integration Examples
// File: lib/strapi.js - API Client Setup
class StrapiClient {
constructor(baseURL, token = null) {
this.baseURL = baseURL;
this.token = token;
this.cache = new Map();
}
setToken(token) {
this.token = token;
}
async request(endpoint, options = {}) {
const url = new URL(endpoint, this.baseURL);
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
};
if (this.token) {
config.headers.Authorization = `Bearer ${this.token}`;
}
try {
const response = await fetch(url.toString(), config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Strapi API Error:', error);
throw error;
}
}
async get(endpoint, params = {}) {
const searchParams = new URLSearchParams();
Object.keys(params).forEach(key => {
if (Array.isArray(params[key])) {
params[key].forEach(value => {
searchParams.append(`${key}[]`, value);
});
} else if (params[key] !== undefined && params[key] !== null) {
searchParams.append(key, params[key]);
}
});
const queryString = searchParams.toString();
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
// Check cache first
const cacheKey = `GET_${url}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const data = await this.request(url);
// Cache for 5 minutes
this.cache.set(cacheKey, data);
setTimeout(() => this.cache.delete(cacheKey), 5 * 60 * 1000);
return data;
}
async post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data),
});
}
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
});
}
async delete(endpoint) {
return this.request(endpoint, {
method: 'DELETE',
});
}
clearCache() {
this.cache.clear();
}
}
// Create and export client instances
export const strapiClient = new StrapiClient(
process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337'
);
export const strapiAdminClient = new StrapiClient(
process.env.STRAPI_ADMIN_URL || 'http://localhost:1337',
process.env.STRAPI_ADMIN_TOKEN
);
// File: lib/strapi-queries.js - Predefined Queries
export const strapiQueries = {
// Articles with population
getArticles: (params = {}) => ({
endpoint: '/articles',
params: {
populate: ['*', 'featured_image', 'category', 'tags', 'author.avatar'],
sort: 'publishedAt:desc',
publicationState: 'live',
...params,
},
}),
getArticleBySlug: (slug, params = {}) => ({
endpoint: '/articles',
params: {
filters: { slug: { $eq: slug } },
populate: ['*', 'featured_image', 'category', 'tags', 'author.avatar'],
publicationState: 'live',
...params,
},
}),
getRelatedArticles: (articleId, categoryId, limit = 3) => ({
endpoint: '/articles',
params: {
filters: {
id: { $ne: articleId },
category: { id: { $eq: categoryId } },
},
populate: ['featured_image', 'category'],
sort: 'publishedAt:desc',
pagination: { limit, start: 0 },
publicationState: 'live',
},
}),
getCategories: () => ({
endpoint: '/categories',
params: {
populate: ['parent'],
sort: 'name:asc',
},
}),
getProducts: (params = {}) => ({
endpoint: '/products',
params: {
populate: ['*', 'images', 'variants.image', 'categories', 'brand', 'reviews'],
sort: 'createdAt:desc',
publicationState: 'live',
...params,
},
}),
getProductsByCategory: (categoryId, params = {}) => ({
endpoint: '/products',
params: {
filters: {
categories: { id: { $eq: categoryId } },
},
populate: ['*', 'images', 'categories'],
sort: 'createdAt:desc',
publicationState: 'live',
...params,
},
}),
searchProducts: (searchTerm, params = {}) => ({
endpoint: '/products',
params: {
filters: {
$or: [
{ name: { $containsi: searchTerm } },
{ description: { $containsi: searchTerm } },
{ short_description: { $containsi: searchTerm } },
{ sku: { $containsi: searchTerm } },
],
},
populate: ['images', 'categories'],
sort: 'name:asc',
publicationState: 'live',
...params,
},
}),
// User-specific queries
getUserOrders: (userId, params = {}) => ({
endpoint: '/orders',
params: {
filters: { user: { id: { $eq: userId } } },
populate: ['items.product', 'items.variant', 'shipping_address', 'billing_address'],
sort: 'createdAt:desc',
...params,
},
}),
createUserCart: (userId) => ({
endpoint: '/carts',
params: {
filters: { user: { id: { $eq: userId } } },
populate: ['items.product', 'items.variant'],
},
}),
};
// File: hooks/useStrapi.js - React Hooks
import { useState, useEffect, useCallback } from 'react';
import { strapiClient } from '../lib/strapi';
export function useStrapiQuery(query, dependencies = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await strapiClient.get(query.endpoint, query.params);
setData(response);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [query.endpoint, query.params]);
useEffect(() => {
fetchData();
}, [fetchData, ...dependencies]);
const refetch = useCallback(() => {
return fetchData();
}, [fetchData]);
return { data, loading, error, refetch };
}
export function useStrapiMutation(endpoint, method = 'POST') {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const mutate = useCallback(async (data) => {
try {
setLoading(true);
setError(null);
let response;
switch (method) {
case 'POST':
response = await strapiClient.post(endpoint, data);
break;
case 'PUT':
response = await strapiClient.put(endpoint, data);
break;
case 'DELETE':
response = await strapiClient.delete(endpoint);
break;
default:
throw new Error(`Unsupported method: ${method}`);
}
return response;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, [endpoint, method]);
return { mutate, loading, error };
}
// File: services/article-service.js - Service Layer
import { strapiClient, strapiQueries } from '../lib/strapi';
export class ArticleService {
static async getArticles(params = {}) {
const query = strapiQueries.getArticles(params);
const response = await strapiClient.get(query.endpoint, query.params);
return {
articles: response.data,
pagination: response.meta.pagination,
};
}
static async getArticleBySlug(slug) {
const query = strapiQueries.getArticleBySlug(slug);
const response = await strapiClient.get(query.endpoint, query.params);
return response.data?.[0] || null;
}
static async getRelatedArticles(articleId, categoryId, limit = 3) {
const query = strapiQueries.getRelatedArticles(articleId, categoryId, limit);
const response = await strapiClient.get(query.endpoint, query.params);
return response.data;
}
static async incrementViewCount(articleId) {
return strapiClient.put(`/articles/${articleId}/view-count`);
}
static async searchArticles(searchTerm, params = {}) {
const query = {
endpoint: '/articles',
params: {
filters: {
$or: [
{ title: { $containsi: searchTerm } },
{ content: { $containsi: searchTerm } },
{ excerpt: { $containsi: searchTerm } },
],
},
populate: ['featured_image', 'category', 'author'],
sort: 'publishedAt:desc',
publicationState: 'live',
...params,
},
};
const response = await strapiClient.get(query.endpoint, query.params);
return {
articles: response.data,
pagination: response.meta.pagination,
};
}
static async getArticlesByTag(tagSlug, params = {}) {
const query = {
endpoint: '/articles',
params: {
filters: {
tags: { slug: { $eq: tagSlug } },
},
populate: ['featured_image', 'category', 'tags', 'author'],
sort: 'publishedAt:desc',
publicationState: 'live',
...params,
},
};
const response = await strapiClient.get(query.endpoint, query.params);
return {
articles: response.data,
pagination: response.meta.pagination,
};
}
}
// File: services/product-service.js - Product Service
export class ProductService {
static async getProducts(params = {}) {
const query = strapiQueries.getProducts(params);
const response = await strapiClient.get(query.endpoint, query.params);
return {
products: response.data,
pagination: response.meta.pagination,
};
}
static async getProductBySlug(slug) {
const query = {
endpoint: '/products',
params: {
filters: { slug: { $eq: slug } },
populate: ['*', 'images', 'variants.image', 'categories', 'brand', 'reviews'],
publicationState: 'live',
},
};
const response = await strapiClient.get(query.endpoint, query.params);
return response.data?.[0] || null;
}
static async searchProducts(searchTerm, params = {}) {
const query = strapiQueries.searchProducts(searchTerm, params);
const response = await strapiClient.get(query.endpoint, query.params);
return {
products: response.data,
pagination: response.meta.pagination,
};
}
static async getProductsByCategory(categorySlug, params = {}) {
const query = {
endpoint: '/products',
params: {
filters: {
categories: { slug: { $eq: categorySlug } },
},
populate: ['images', 'categories'],
sort: 'createdAt:desc',
publicationState: 'live',
...params,
},
};
const response = await strapiClient.get(query.endpoint, query.params);
return {
products: response.data,
pagination: response.meta.pagination,
};
}
static async getFeaturedProducts(limit = 8) {
const query = strapiQueries.getProducts({
filters: { featured: true },
pagination: { limit },
});
const response = await strapiClient.get(query.endpoint, query.params);
return response.data;
}
}
// File: components/ArticleList.js - Frontend Component Example
import React, { useState } from 'react';
import { ArticleService } from '../services/article-service';
export default function ArticleList({ category, tag, limit = 10 }) {
const [articles, setArticles] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
const [pagination, setPagination] = useState(null);
useEffect(() => {
fetchArticles();
}, [category, tag, page]);
const fetchArticles = async () => {
try {
setLoading(true);
setError(null);
const params = {
pagination: { limit, page },
};
if (category) {
params.filters = { category: { slug: { $eq: category } } };
}
if (tag) {
params.filters = {
...params.filters,
tags: { slug: { $eq: tag } },
};
}
const result = await ArticleService.getArticles(params);
setArticles(result.articles);
setPagination(result.pagination);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const handlePageChange = (newPage) => {
setPage(newPage);
};
if (loading) {
return <div>Loading articles...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
return (
<div>
<h1>Articles</h1>
<div className="article-grid">
{articles.map((article) => (
<ArticleCard key={article.id} article={article} />
))}
</div>
{pagination && (
<Pagination
currentPage={page}
totalPages={pagination.pageCount}
onPageChange={handlePageChange}
/>
)}
</div>
);
}
// File: lib/strapi-graphql.js - GraphQL Client
import { GraphQLClient, gql } from 'graphql-request';
class StrapiGraphQLClient {
constructor(baseURL, token = null) {
this.client = new GraphQLClient(`${baseURL}/graphql`, {
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
}
async query(query, variables = {}) {
try {
return await this.client.request(query, variables);
} catch (error) {
console.error('Strapi GraphQL Error:', error);
throw error;
}
}
async mutation(mutation, variables = {}) {
try {
return await this.client.request(mutation, variables);
} catch (error) {
console.error('Strapi GraphQL Error:', error);
throw error;
}
}
}
export const strapiGraphQL = new StrapiGraphQLClient(
process.env.NEXT_PUBLIC_STRAPI_URL || 'http://localhost:1337',
process.env.STRAPI_API_TOKEN
);
// GraphQL Queries
export const GET_ARTICLES = gql`
query GetArticles(
$pagination: PaginationArg = { page: 1, pageSize: 10 }
$filters: ArticleFiltersInput = {}
$sort: [String] = ["publishedAt:desc"]
$publicationState: PublicationState = LIVE
) {
articles(pagination: $pagination, filters: $filters, sort: $sort, publicationState: $publicationState) {
data {
id
attributes {
title
slug
excerpt
publishedAt
featured_image {
data {
attributes {
url
alternativeText
}
}
}
category {
data {
attributes {
name
slug
color
}
}
}
tags {
data {
attributes {
name
slug
}
}
}
author {
data {
attributes {
name
avatar {
data {
attributes {
url
}
}
}
}
}
}
}
}
meta {
pagination {
page
pageSize
pageCount
total
}
}
}
}
`;
export const GET_ARTICLE_BY_SLUG = gql`
query GetArticleBySlug($slug: String!, $publicationState: PublicationState = LIVE) {
articles(filters: { slug: { eq: $slug } }, publicationState: $publicationState) {
data {
id
attributes {
title
slug
content
publishedAt
featured_image {
data {
attributes {
url
alternativeText
caption
}
}
}
category {
data {
attributes {
name
slug
description
}
}
}
tags {
data {
attributes {
name
slug
}
}
}
author {
data {
attributes {
name
bio
avatar {
data {
attributes {
url
}
}
}
}
}
}
seo {
meta_title
meta_description
keywords
}
}
}
}
}
`;
💻 Strapi Plugins & Deployment javascript
🔴 complex
⭐⭐⭐⭐⭐
Vollständige Plugin-Entwicklung, Anpassung und Deployment-Strategien für Produktionsumgebungen
⏱️ 50 min
🏷️ strapi, plugins, deployment, production
Prerequisites:
Strapi basics, Plugin development, Docker, DevOps, Database concepts
// Strapi Plugins & Deployment Examples
// File: plugins/custom-controllers/server/controllers/custom.js
'use strict';
/**
* Custom controller example
*/
module.exports = {
// Custom route handler
async customAction(ctx) {
try {
const { param } = ctx.params;
const { query } = ctx.query;
// Custom business logic
const result = await strapi.service('api::custom.custom').processCustomData(param, query);
ctx.send({
message: 'Custom action completed successfully',
data: result,
timestamp: new Date().toISOString()
});
} catch (error) {
ctx.status = 500;
ctx.send({
message: 'Custom action failed',
error: error.message
});
}
},
// Bulk operations
async bulkUpdate(ctx) {
try {
const { ids, data } = ctx.request.body;
// Validate input
if (!Array.isArray(ids) || ids.length === 0) {
return ctx.badRequest('Invalid IDs array');
}
// Process bulk update
const results = await strapi.service('api::custom.custom').bulkUpdate(ids, data);
ctx.send({
message: `Successfully updated ${results.updated} items`,
updated: results.updated,
failed: results.failed
});
} catch (error) {
ctx.status = 500;
ctx.send({
message: 'Bulk update failed',
error: error.message
});
}
},
// Export data
async export(ctx) {
try {
const { format = 'json', filters = {} } = ctx.query;
const data = await strapi.service('api::custom.custom').exportData(filters);
if (format === 'csv') {
ctx.set('Content-Type', 'text/csv');
ctx.set('Content-Disposition', 'attachment; filename="export.csv"');
return ctx.send(data.csv);
} else {
ctx.set('Content-Type', 'application/json');
ctx.set('Content-Disposition', 'attachment; filename="export.json"');
return ctx.send(data.json);
}
} catch (error) {
ctx.status = 500;
ctx.send({
message: 'Export failed',
error: error.message
});
}
}
};
// File: plugins/custom-controllers/server/services/custom.js
'use strict';
/**
* Custom service example
*/
module.exports = ({ strapi }) => ({
// Process custom data
async processCustomData(param, query = {}) {
// Custom business logic
const entities = await strapi.query('api::entity.entity').findMany({
where: {
customField: param,
...query.filters
},
populate: ['relations']
});
// Transform data
const processedData = entities.map(entity => ({
...entity,
processedValue: this.calculateProcessedValue(entity),
metadata: this.generateMetadata(entity)
}));
return {
count: processedData.length,
data: processedData
};
},
// Calculate processed value
calculateProcessedValue(entity) {
// Example calculation
return entity.field1 * entity.field2 + entity.field3;
},
// Generate metadata
generateMetadata(entity) {
return {
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
version: this.getEntityVersion(entity)
};
},
// Get entity version
getEntityVersion(entity) {
return `v${entity.id}.${entity.updatedAt.getTime()}`;
},
// Bulk update implementation
async bulkUpdate(ids, data) {
const results = {
updated: 0,
failed: []
};
for (const id of ids) {
try {
await strapi.query('api::entity.entity').update({
where: { id },
data: {
...data,
updatedBy: 1 // Current user ID
}
});
results.updated++;
} catch (error) {
results.failed.push({
id,
error: error.message
});
}
}
return results;
},
// Export data implementation
async exportData(filters = {}) {
const entities = await strapi.query('api::entity.entity').findMany({
where: filters,
populate: ['relations']
});
// Transform to CSV
const csv = this.convertToCSV(entities);
return {
json: entities,
csv: csv
};
},
// Convert to CSV
convertToCSV(data) {
if (data.length === 0) return '';
const headers = Object.keys(data[0]);
const csvRows = [headers.join(',')];
for (const row of data) {
const values = headers.map(header => {
const value = row[header];
if (value === null || value === undefined) return '';
if (typeof value === 'object') return JSON.stringify(value);
return `"${String(value).replace(/"/g, '\"')}"`;
});
csvRows.push(values.join(','));
}
return csvRows.join('\n');
}
});
// File: plugins/webhooks/server/register.js
module.exports = {
register({ strapi }) {
// Register webhook events
strapi webhook.addEvent('entry.create');
strapi webhook.addEvent('entry.update');
strapi.websocket.addEvent('entry.delete');
},
async bootstrap({ strapi }) {
// Bootstrap logic
console.log('Webhook plugin loaded');
},
async destroy({ strapi }) {
// Cleanup logic
console.log('Webhook plugin destroyed');
}
};
// File: plugins/webhooks/server/webhooks.js
module.exports = {
'entry.create': async (event) => {
const { model, uid, entry } = event;
// Send webhook notification
await strapi.plugin('webhooks').service('webhook').trigger({
event: 'entry.create',
data: {
model,
uid,
entry,
timestamp: new Date().toISOString()
}
});
// Log event
strapi.log.info(`Webhook triggered for ${model} creation: ${entry.id}`);
},
'entry.update': async (event) => {
const { model, uid, entry, changes } = event;
// Only send webhook if there are actual changes
if (changes && Object.keys(changes).length > 0) {
await strapi.plugin('webhooks').service('webhook').trigger({
event: 'entry.update',
data: {
model,
uid,
entry,
changes,
timestamp: new Date().toISOString()
}
});
}
},
'entry.delete': async (event) => {
const { model, uid, entry } = event;
await strapi.plugin('webhooks').service('webhook').trigger({
event: 'entry.delete',
data: {
model,
uid,
entry,
timestamp: new Date().toISOString()
}
});
}
};
// File: plugins/analytics/server/services/analytics.js
'use strict';
module.exports = ({ strapi }) => ({
// Track page view
async trackPageView(data) {
const { url, referrer, userAgent, ip } = data;
const pageView = await strapi.query('api::page-view.page-view').create({
data: {
url,
referrer,
userAgent,
ip,
timestamp: new Date(),
date: new Date().toISOString().split('T')[0]
}
});
// Update daily stats
await this.updateDailyStats('pageViews', pageView.date);
return pageView;
},
// Track event
async trackEvent(data) {
const { type, category, action, label, value, url, userAgent, ip } = data;
const event = await strapi.query('api::analytics-event.analytics-event').create({
data: {
type,
category,
action,
label,
value,
url,
userAgent,
ip,
timestamp: new Date(),
date: new Date().toISOString().split('T')[0]
}
});
// Update daily stats
await this.updateDailyStats('events', event.date);
return event;
},
// Update daily statistics
async updateDailyStats(type, date) {
const existingStat = await strapi.query('api::daily-stat.daily-stat').findOne({
where: { date, type }
});
if (existingStat) {
await strapi.query('api::daily-stat.daily-stat').update({
where: { id: existingStat.id },
data: {
count: existingStat.count + 1,
updatedAt: new Date()
}
});
} else {
await strapi.query('api::daily-stat.daily-stat').create({
data: {
date,
type,
count: 1
}
});
}
},
// Get analytics data
async getAnalytics(filters = {}) {
const { startDate, endDate, type } = filters;
const whereClause = {};
if (startDate && endDate) {
whereClause.date = {
$gte: startDate,
$lte: endDate
};
}
if (type) {
whereClause.type = type;
}
const stats = await strapi.query('api::daily-stat.daily-stat').findMany({
where: whereClause,
orderBy: { date: 'asc' }
});
return stats;
},
// Get popular content
async getPopularContent(limit = 10) {
const pageViews = await strapi.query('api::page-view.page-view').findMany({
where: {
date: {
$gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // Last 30 days
}
}
});
// Group by URL and count
const urlCounts = pageViews.reduce((acc, view) => {
acc[view.url] = (acc[view.url] || 0) + 1;
return acc;
}, {});
// Sort by count and limit
const popularUrls = Object.entries(urlCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, limit)
.map(([url, count]) => ({ url, count }));
return popularUrls;
}
});
// File: config/plugins.js - Plugin Configuration
module.exports = {
// Enable plugins
'users-permissions': {
enabled: true,
config: {
jwtSecret: process.env.JWT_SECRET || 'default-secret',
register: {
allowedFields: ['username', 'email', 'password'],
uniqueFields: ['email', 'username'],
},
confirmEmail: false,
},
},
// Upload configuration
upload: {
enabled: true,
config: {
provider: 'local', // or 'aws-s3'
providerOptions: {
local: {
path: './public/uploads'
},
awsS3: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
bucket: process.env.AWS_S3_BUCKET,
},
},
},
},
// Email configuration
'email': {
enabled: true,
config: {
provider: 'sendgrid', // or 'nodemailer'
providerOptions: {
sendgrid: {
apiKey: process.env.SENDGRID_API_KEY,
},
nodemailer: {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
},
},
settings: {
defaultFrom: process.env.DEFAULT_FROM_EMAIL,
defaultReplyTo: process.env.DEFAULT_REPLY_TO_EMAIL,
},
},
},
// i18n configuration
i18n: {
enabled: true,
config: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr', 'de', 'it', 'pt', 'zh'],
fallbackLocale: 'en',
},
},
// GraphQL configuration
graphql: {
enabled: true,
config: {
endpoint: '/graphql',
shadowCRUD: true,
playgroundAlways: process.env.NODE_ENV !== 'production',
depthLimit: 7,
apolloServer: {
introspection: process.env.NODE_ENV !== 'production',
},
},
},
// Documentation configuration
'documentation': {
enabled: true,
config: {
openapi: {
version: '1.0.0',
info: {
title: 'My API Documentation',
description: 'Comprehensive API documentation',
contact: {
name: 'API Support',
email: '[email protected]',
},
},
servers: [
{
url: 'http://localhost:1337/api',
description: 'Development server',
},
],
security: [
{
bearerAuth: [],
},
],
},
},
},
// Webhook configuration
webhook: {
enabled: true,
config: {
allowedOrigins: ['https://example.com', 'https://app.example.com'],
},
},
// Redis cache configuration
'redis': {
enabled: true,
config: {
connections: {
default: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD,
db: 0,
},
},
},
},
};
// File: docker-compose.prod.yml - Production Deployment
version: '3.8'
services:
# Strapi application
strapi:
build:
context: .
dockerfile: Dockerfile.prod
image: strapi-app:latest
restart: unless-stopped
environment:
NODE_ENV: production
DATABASE_CLIENT: postgres
DATABASE_HOST: postgres
DATABASE_PORT: 5432
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
APP_KEYS: ${APP_KEYS}
API_TOKEN_SALT: ${API_TOKEN_SALT}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
TRANSFER_TOKEN_SALT: ${TRANSFER_TOKEN_SALT}
REDIS_HOST: redis
REDIS_PORT: 6379
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
AWS_S3_BUCKET: ${AWS_S3_BUCKET}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
ports:
- "1337:1337"
volumes:
- ./public/uploads:/app/public/uploads
networks:
- strapi-network
# PostgreSQL database
postgres:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_DB: ${DATABASE_NAME}
POSTGRES_USER: ${DATABASE_USERNAME}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./config/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USERNAME} -d ${DATABASE_NAME}"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- strapi-network
# Redis cache
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- strapi-network
# Nginx reverse proxy
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
- strapi
networks:
- strapi-network
# Monitoring with Prometheus
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
networks:
- strapi-network
# Grafana for visualization
grafana:
image: grafana/grafana:latest
restart: unless-stopped
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana:/etc/grafana/provisioning:ro
networks:
- strapi-network
volumes:
postgres_data:
redis_data:
prometheus_data:
grafana_data:
networks:
strapi-network:
driver: bridge
# File: Dockerfile.prod - Production Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the app
RUN npm run build
# Production stage
FROM node:18-alpine AS production
# Install dumb-init for signal handling
RUN apk add --no-cache dumb-init
# Create app user
RUN addgroup -g 1001 -S nodejs && \
adduser -S strapi -u 1001
WORKDIR /app
# Copy built app
COPY --from=builder --chown=strapi:nodejs /app .
# Create uploads directory
RUN mkdir -p public/uploads && \
chown -R strapi:nodejs public/uploads
# Switch to app user
USER strapi
# Expose port
EXPOSE 1337
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:1337/health || exit 1
# Start the app
ENTRYPOINT ["dumb-init", "--"]
CMD ["npm", "start"]
# File: scripts/backup.sh - Backup Script
#!/bin/bash
# Configuration
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
STRAPI_CONTAINER="strapi_strapi_1"
DB_CONTAINER="strapi_postgres_1"
# Create backup directory
mkdir -p $BACKUP_DIR
# Backup database
echo "Backing up database..."
docker exec $DB_CONTAINER pg_dump -U $DATABASE_USERNAME $DATABASE_NAME > $BACKUP_DIR/db_backup_$DATE.sql
# Backup uploads
echo "Backing up uploads..."
docker cp $STRAPI_CONTAINER:/app/public/uploads $BACKUP_DIR/uploads_$DATE
# Compress backups
echo "Compressing backups..."
tar -czf $BACKUP_DIR/strapi_backup_$DATE.tar.gz -C $BACKUP_DIR db_backup_$DATE.sql uploads_$DATE
# Clean up
rm -f $BACKUP_DIR/db_backup_$DATE.sql
rm -rf $BACKUP_DIR/uploads_$DATE
# Remove old backups (keep last 7 days)
find $BACKUP_DIR -name "strapi_backup_*.tar.gz" -mtime +7 -delete
echo "Backup completed: $BACKUP_DIR/strapi_backup_$DATE.tar.gz"