Strapi

Open-source headless CMS built with Node.js/TypeScript. Provides REST/GraphQL APIs with high customizability and developer-friendly features.

CMSHeadlessNode.jsJavaScriptAPIGraphQLREST
License
MIT
Language
JavaScript/TypeScript
Pricing
Free plan available
Official Site
Visit Official Site

CMS

Strapi

Overview

Strapi is an open-source headless CMS designed for modern web application development.

Details

Strapi is a content management system that adopts a headless (decoupled) architecture, separating frontend and backend. Built on Node.js and Koa.js, it provides developers with a flexible API development environment. It supports both REST and GraphQL APIs and can be combined with any frontend technology (React, Vue.js, Angular, Next.js, etc.). The admin panel is intuitive and user-friendly, allowing visual management of content types, user management, permission settings, and plugin management. It supports PostgreSQL, MySQL, SQLite, and MariaDB, and offers Docker deployment and cloud deployment to AWS/GCP/Azure. The enterprise version provides advanced permission management, RBAC, SSO, and workflow features.

Pros and Cons

Pros

  • Headless Design: Rich choice of frontends enables flexible development
  • REST and GraphQL: Supports both API types
  • Self-hosted: Complete data control on-premises
  • Intuitive Admin Panel: Easy-to-use UI even for non-technical users
  • Rich Plugins: Easy functionality extension
  • Open Source: Free to use with active community
  • Multi-language Support: Built-in internationalization features

Cons

  • Self-management: Infrastructure management and maintenance required
  • Learning Curve: Understanding headless design is necessary
  • Resource Consumption: Server resources as Node.js application
  • Complex Configuration: Technical knowledge required for advanced customization
  • Scalability: Additional configuration needed for large-scale traffic

Key Links

Usage Examples

Installation and Initial Setup

# Create Strapi project
npx create-strapi-app@latest my-project --quickstart

# Start development server
npm run develop

# Access admin panel: http://localhost:1337/admin

Content Type Definition

// config/api/article/models/article.settings.json
{
  "kind": "collectionType",
  "collectionName": "articles",
  "info": {
    "name": "article"
  },
  "options": {
    "increments": true,
    "timestamps": true
  },
  "attributes": {
    "title": {
      "type": "string",
      "required": true
    },
    "content": {
      "type": "richtext"
    },
    "author": {
      "type": "relation",
      "relation": "manyToOne",
      "target": "plugin::users-permissions.user"
    },
    "published_at": {
      "type": "datetime"
    },
    "slug": {
      "type": "uid",
      "targetField": "title"
    }
  }
}

API Usage

// Data fetching with REST API
const response = await fetch('http://localhost:1337/api/articles');
const data = await response.json();

// Data fetching with GraphQL
const ARTICLES_QUERY = `
  query GetArticles {
    articles {
      data {
        id
        attributes {
          title
          content
          published_at
          author {
            data {
              attributes {
                username
              }
            }
          }
        }
      }
    }
  }
`;

const response = await fetch('http://localhost:1337/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ query: ARTICLES_QUERY }),
});

Custom API Endpoint

// api/article/controllers/article.js
'use strict';

const { createCoreController } = require('@strapi/strapi').factories;

module.exports = createCoreController('api::article.article', ({ strapi }) => ({
  // Custom endpoint
  async findPublished(ctx) {
    const entries = await strapi.entityService.findMany('api::article.article', {
      filters: {
        published_at: {
          $notNull: true,
        },
      },
      populate: {
        author: true,
      },
    });

    return entries;
  },

  // Get popular articles
  async findPopular(ctx) {
    const entries = await strapi.entityService.findMany('api::article.article', {
      sort: { views: 'desc' },
      limit: 10,
    });

    return entries;
  }
}));

Plugin Creation

// plugins/custom-plugin/server/controllers/my-controller.js
'use strict';

module.exports = {
  index(ctx) {
    ctx.body = strapi
      .plugin('custom-plugin')
      .service('myService')
      .getWelcomeMessage();
  },
};

// plugins/custom-plugin/server/services/my-service.js
'use strict';

module.exports = {
  getWelcomeMessage() {
    return 'Welcome to Strapi 🚀';
  },
};

Docker Configuration

# Dockerfile
FROM node:18-alpine
WORKDIR /opt/app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 1337
CMD ["npm", "start"]
# docker-compose.yml
version: '3'
services:
  strapi:
    container_name: strapi
    build: .
    image: strapi:latest
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_CLIENT: postgres
      DATABASE_HOST: strapiDB
      DATABASE_PORT: 5432
      DATABASE_NAME: strapi
      DATABASE_USERNAME: strapi
      DATABASE_PASSWORD: strapi
    ports:
      - '1337:1337'
    networks:
      - strapi
    depends_on:
      - strapiDB

  strapiDB:
    container_name: strapiDB
    platform: linux/amd64
    restart: unless-stopped
    env_file: .env
    image: postgres:14.5-alpine
    environment:
      POSTGRES_USER: strapi
      POSTGRES_PASSWORD: strapi
      POSTGRES_DB: strapi
    ports:
      - '5432:5432'
    networks:
      - strapi

networks:
  strapi:
    name: Strapi
    driver: bridge