Building a Headless CMS with Laravel: A Step-by-Step Guide

In the modern era of web development, the concept of a Headless CMS has gained immense popularity. By decoupling the backend content management system from the frontend display layer, developers can deliver content to multiple platforms seamlessly. Laravel, a powerful PHP framework, provides an excellent foundation for creating a robust and scalable Headless CMS.

In this guide, I’ll walk through the steps to build one from scratch.

Prerequisites

Before diving in, make sure you have the following:

  • PHP (>=8.1)
  • Composer
  • Laravel Framework (latest version)
  • A database (e.g., MySQL, PostgreSQL, or SQLite)
  • Basic knowledge of Laravel and REST APIs

Step 1: Setting Up Laravel

  1. Install Laravel

Create a new Laravel project by running:

composer create-project --prefer-dist laravel/laravel headless-cms

2. Configure Environment

Set up your .env file with database credentials:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=headless_cms
DB_USERNAME=root
DB_PASSWORD=secret

3. Migrate Default Tables

Run the following command to migrate the default Laravel tables:

php artisan migrate

Step 2: Design Your Content Models

For this example, let’s create a blog system with Post and Category models.

  1. Create Models and Migrations
php artisan make:model Post -m
php artisan make:model Category -m

2. Define Database Structure

Update the migration files:

database/migrations/YYYY_MM_DD_create_posts_table.php:

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('content');
        $table->foreignId('category_id')->constrained()->onDelete('cascade');
        $table->timestamps();
    });
}

database/migrations/YYYY_MM_DD_create_categories_table.php:

public function up()
{
    Schema::create('categories', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->timestamps();
    });
}

3. Run Migrations

Execute the migrations to create the tables:

php artisan migrate

Step 3: Build API Endpoints

  1. Set Up API Routes

Open routes/api.php and define the routes:

use App\Http\Controllers\PostController;
use App\Http\Controllers\CategoryController;

Route::apiResource('posts', PostController::class);
Route::apiResource('categories', CategoryController::class);

2. Create Controllers

php artisan make:controller PostController --api
php artisan make:controller CategoryController --api

3. Implement Controller Logic

In PostController.php:

public function index()
{
    return Post::with('category')->get();
}

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|string|max:255',
        'content' => 'required',
        'category_id' => 'required|exists:categories,id',
    ]);

    return Post::create($validated);
}

public function show(Post $post)
{
    return $post->load('category');
}

public function update(Request $request, Post $post)
{
    $validated = $request->validate([
        'title' => 'sometimes|string|max:255',
        'content' => 'sometimes',
        'category_id' => 'sometimes|exists:categories,id',
    ]);

    $post->update($validated);

    return $post;
}

public function destroy(Post $post)
{
    $post->delete();
    return response()->noContent();
}

Repeat similar logic for CategoryController.

4. Test API with Postman or cURL

Ensure endpoints like /api/posts and /api/categories are functioning correctly.

Step 4: Add Authentication

Install Laravel Sanctum

composer require laravel/sanctum

Publish and Migrate Sanctum Config

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Protect Routes

Wrap your API routes with the auth:sanctum middleware:

Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('posts', PostController::class);
    Route::apiResource('categories', CategoryController::class);
});

Generate Tokens for Users

Add a method in your User model:

public function generateToken()
{
    return $this->createToken('api-token')->plainTextToken;
}

Step 5: Frontend Integration

Since this is a Headless CMS, you can use any frontend framework (React, Vue.js, Angular) to consume the API. Here’s an example of fetching data using React:

import React, { useEffect, useState } from 'react';

function App() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch('http://localhost:8000/api/posts')
      .then(response => response.json())
      .then(data => setPosts(data));
  }, []);

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Conclusion

Congratulations! You’ve built a Headless CMS using Laravel. This system can be extended further by adding features like role-based access control (RBAC), advanced filtering, or GraphQL support. The possibilities are endless when you decouple your frontend and backend.

Happy coding!

FAQs

Q: What is headless CMS?
A Headless CMS is a backend content management system that delivers content via an API. it allows flexibility for frontend frameworks like React or Vue.js. Laravel is a great choice for this because it offers powerful routing, authentication, and database management, which makes it easier to build a scalable and secure CMS.  

Q: How does authentication work?
Laravel Sanctum handles authentication by issuing API tokens instead of sessions. Using auth:sanctum middleware, only authenticated users can access protected routes, which ensures secure content management.  
Q: Can I use this with React or Vue?
Yes! A Laravel Headless CMS provides API endpoints that work with React, Vue.js, Angular, or any frontend framework. This allows for flexible and multi-platform content delivery.
Farhan Hasin Chowdhury
Farhan Hasin Chowdhury
Senior Software Engineer

Farhan is a passionate full-stack developer and author. He's a huge fan of the open-source mindset and loves sharing his knowledge with the community.

Expertise
  • Laravel
  • MySQL
  • Vue.js
  • Node.js
  • AWS
  • +16

Ready to start?

Get in touch or schedule a call.