Create Laravel Blog with Strapi v4

Shahed Nasser - Dec 20 '21 - - Dev Community

This article was originally published on my personal blog.

Laravel is one of the most popular web frameworks that allow you to create secure and scalable websites. With Laravel, you can create almost any website, ranging from simple portfolio websites to full-fledged e-commerce solutions.

Strapi is a headless CMS framework that has been gaining a lot of attention. With Strapi, you can easily add CMS functionalities to your website, regardless of the technology being used. Strapi also removes the hassles of creating a database and models tailored to your CMS needs as it is customizable to the point that you can create your own models and entities, also called content types.

In this tutorial, you'll learn how to create a blog in Laravel using Strapi. The blog will have posts, tags, and allow users to post comments. You can find the code for this tutorial in this GitHub repository.

Prerequisites

Before you start, you need the following tools on your machine:

  1. Node.js. Only versions 12 and 14 are supported by Strapi, and 14 is recommended.
  2. PHP >= v7.3
  3. Composer

Please note that this tutorial will be using Strapi v4 and Laravel v8.9

Setup Strapi

Start by setting up Strapi. In your terminal, run the following command:

npx create-strapi-app@latest strapi --quickstart
Enter fullscreen mode Exit fullscreen mode

Once the command is done, the server will start at localhost:1337 and a new page will open in a browser. It will be a registration form, where you need to create an admin account on Strapi.

Once you're done, you'll be redirected to the main dashboard.

Create Laravel Blog with Strapi v4

Create Content-Types

Next, you'll create the content types necessary for the blog. You'll create content types for the posts and tags that the posts will use. You'll also create a comment content type which will be filled from our blog on Laravel.

Start by clicking on Create your first Content type in the dashboard. This will open a new page to create content types. Under the Content Types Builder sidebar, click on Create new collection type under Collection type. This will open a pop-up where you can enter basic and advanced settings.

You'll first create the tags content type. Enter in the Display Name field in the popup Tag. This will automatically generate the singular and plural forms of the content type.

Create Laravel Blog with Strapi v4

Once you're done, click Continue. You can now choose the fields in this content type. tags will only have a name field other than their id. So, click on the Text field type. Then, enter in the Name field name.

Create Laravel Blog with Strapi v4

In the Advanced Settings tab, check the Required checkbox to ensure that all tags have a name.

Since that's the only field you'll add for the Tag content type, click on Finish. Then, when the pop-up closes, click on the Save button at the top right. This will restart the server. Every time you create a content type, the server is restarted.

Next, you'll create the Post content type. Again, click on Create new collection type. In the pop up that opens, enter for Display Name Post, then click on Continue.

For posts, there will be fields for title, content, image, date posted, and tags that the post falls under.

For the title field, choose the Text field and make it required as we did earlier. Once done, click on Add another field.

For the content field, choose the Rich text field, and make it required.

For the image field, choose the Media field, and choose for Type "Single media". In the Advanced Settings tab, change the allowed file types under "Select allowed types of media" to only Images. Make the field required as well.

For the date_posted field, choose the Date field, and choose for Type "datetime". Mark this field required as well.

Finally, for the tags field, choose the Relation field, then for the relation type choose "Post belongs to many Tags".

Create Laravel Blog with Strapi v4

Once you're done, click on Finish, then click on Save at the top right. This will save the new content type and restart the server.

Finally, you need to create the Comment content type. Just like you did with the previous content types, create a new content type with the name Comment.

The Comment content type will have 3 fields. The first is an Email field with the name field. Make sure to set it as required.

The second field is a Rich text field with the name content. This is where the user's comment will go.

The third field is a Relation field between Comment and Post. The relation should be "Post has many Comments".

Create Laravel Blog with Strapi v4

Note that when you create this field a new field will be added automatically in Post called comments.

Once you're done, click on Finish, then click on Save at the top right. This will save the new content type and restart the server.

Our content types are ready!

Add Content

The next step would be to add content. Click on Content Manager in the sidebar. Start by adding a few tags by clicking on Tag in the Content Manager sidebar, then click on Add new entry at the top right.

When you create content, make sure you click Publish after saving the content.

Next, add posts the same way. You can use Lorem Ipsum Generator if you want to create mock content.

Change Permissions

The last step left is to make posts and tags public so that you can consume them in Laravel.

First, you'll create an API token to use for your requests. In the sidebar, click Settings, then API Token. Click on Add Entry at the top right.

In this form, enter the name of the token. This is just useful to remember what your API tokens are for. You can also enter a description.

In the Token type field, choose Full Access.

Create Laravel Blog with Strapi v4

Once you're done, click on Save at the top right. This will create a new API token and the API token will be shown to you only once when you create it. So, copy the API token and store it somewhere as you'll use it later.

Next, you'll modify the permissions for authenticated users to be able to query content types and add new entries.

On the sidebar, click Settings, then Roles in the Settings sidebar.

You'll see two roles: Authenticated and Public. Click on the pencil icon on the Authenticated row.

Scroll down and you'll see that for each content type you can select what this role can access. Check Select All for Post, Tag, and Comment, then click Save.

Create Laravel Blog with Strapi v4

Setup Laravel

Now that Strapi is ready, you'll get started with Laravel.

Run the following command to create a new Laravel project:

composer create-project laravel/laravel blog
Enter fullscreen mode Exit fullscreen mode

Once this command is done, change to the directory created:

cd blog
Enter fullscreen mode Exit fullscreen mode

You can then start the server with the following command:

php artisan serve
Enter fullscreen mode Exit fullscreen mode

This will start the server at localhost:8000.

Add Environment Variables

Before you can make requests to Strapi, you need to add 2 environment variables. Add the following environment variables to .env:

STRAPI_URL=http://localhost:1337
STRAPI_API_TOKEN=
Enter fullscreen mode Exit fullscreen mode

The first is the URL to Strapi. You can change it if it's not the same local URL. The second is the API token you created earlier. Paste it here after the = sign.

Add Home Page

On the home page, you'll query all posts in Strapi and display them.

Run the following command in your terminal to create a new controller:

php artisan make:controller BlogController
Enter fullscreen mode Exit fullscreen mode

Then, open app/Http/Controllers/BlogController.php and the following method in the class:

public function home () {
    //retrieve the posts from Strapi
    $response = Http::withToken(env('STRAPI_API_TOKEN'))->get(env('STRAPI_URL') . '/api/posts?populate=image,tags');
    $posts = [];

    if ($response->failed()) {
        if (isset($data['error'])) {
            Log::error('Server error: ' . $data['error']['message']);
        } else {
            Log::error('Request Failed');
        }
    } else {
        //get posts from response
        $posts = $response->json('data');
    }

    return view('home', ['posts' => $posts]);
}
Enter fullscreen mode Exit fullscreen mode

First, you query Strapi using Laravel's HTTP Client. You use withToken to pass it the API token from .env using the env helper function. Then, you send a get request to the endpoint localhost:1337/api/posts?populate=image,tags.

Notice that localhost:1337 also is retrieved from .env. As for the endpoint path, Strapi has a conventional path for all its content types. When querying a collection, the pattern for the endpoint is /api/{collection_name}.

When you use Strapi's API, you can pass it a lot of useful parameters that allow you to filter, sort, paginate the data, and more. Here, you use the populate parameter which allows you to retrieve a content type with its relations. You use it to retrieve the post with its image and tags.

After sending the request, you can check if the request failed using $response->failed(). If the request failed, you log the error. If not, you set $posts to the data parameter in the response body. Note that you can use the json method to retrieve the parameters from a JSON response, optionally passing it a parameter name as the first element.

Next, you need to add the home view. Create the file resources/views/home.blade.php with the following content:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Blog</title>

        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    </head>
    <body class="antialiased bg-light">
        <div class="container mt-4 py-3 mx-auto bg-white rounded shadow-sm">
            <div class="row">
                @forelse ($posts as $post)
                    <div class="col-2 col-md-4">
                        <div class="card">
                            <img src="{{ env('STRAPI_URL') . $post['attributes']['image']['data']['attributes']['formats']['medium']['url'] }}" 
                                class="card-img-top" alt="{{ $post['attributes']['image']['data']['attributes']['alternativeText'] }}">
                            <div class="card-body">
                                <h5 class="card-title">{{ $post['attributes']['title'] }}</h5>
                                <p class="card-text">{{ substr($post['attributes']['content'], 0, 50) }}...</p>
                                <a href="/post/{{ $post['id'] }}" class="btn btn-primary">Read More</a>
                            </div>
                            <div class="card-footer">
                                @if(count($post['attributes']['tags']['data']))
                                    @foreach ($post['attributes']['tags']['data'] as $tag)
                                        <span class="badge bg-success">{{ $tag['attributes']['name'] }}</span>
                                    @endforeach
                                @endif
                            </div>
                        </div>
                    </div>
                @empty
                    <div class="col">
                        <div class="card">
                            <div class="card-body">
                                This is some text within a card body.
                            </div>
                        </div>
                    </div>
                @endforelse
            </div>
        </div>
    </body>
</html>

Enter fullscreen mode Exit fullscreen mode

This just displays the posts as cards using Bootstrap. Notice that the content type entries that Strapi return has the following format:

{
    "data": {
        "id",
        "attributes": {
            "title",
            ...
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

So, you'll find the content type's fields inside the attributes key of data.

Finally, change the current route in routes/web.php to the following:

Route::get('/', [\App\Http\Controllers\BlogController::class, 'home']);
Enter fullscreen mode Exit fullscreen mode

Let's test it out. Make sure that both Laravel and Strapi's servers are running. Then, open localhost:8000. You'll see the posts you added as cards.

Create Laravel Blog with Strapi v4

Add View Post Page

Next, you'll add the page to view a post. This page receives the post ID as a parameter, then queries the post's data from Strapi.

In app/Http/Controllers/BlogController.php add a new method:

public function viewPost ($id) {
        //retrieve the post from Strapi
        $response = Http::withToken(env('STRAPI_API_TOKEN'))->get(env('STRAPI_URL') . '/api/posts/' . $id . '?populate=image,tags,comments');

        if ($response->failed()) {
            if (isset($data['error'])) {
                Log::error('Server error: ' . $data['error']['message']);
            } else {
                Log::error('Request Failed');
            }

            return response()->redirectTo('/');
        }

        //get post from response
        $post = $response->json('data');

        return view('post', ['post' => $post]);
 }
Enter fullscreen mode Exit fullscreen mode

In this method, you use the $id parameter, which is the post ID, to send a request to Strapi's single entry endpoint. The endpoint's pattern is /api/{collection_name}/{id}. Similar to the previous endpoint, you can also pass it parameters like populate.

If the request fails, you redirect the user to the home page and log the error. If the request is done successfully, you retrieve the post from the response's body and render the view post.

Now, create resources/views/post.blade.php with the following content:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>{{ $post['attributes']['title'] }} - Blog</title>

        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    </head>
    <body class="antialiased bg-light">
        <div class="container mt-4 py-3 px-5 mx-auto bg-white rounded shadow-sm">
            <h1>{{ $post['attributes']['title'] }}</h1>
            <small class="text-muted d-block">{{ $post['attributes']['date_posted'] }}</small>
            <img src="{{ env('STRAPI_URL') . $post['attributes']['image']['data']['attributes']['formats']['medium']['url'] }}" 
                                class="img-fluid mx-auto d-block my-3" alt="{{ $post['attributes']['image']['data']['attributes']['alternativeText'] }}">
            @if(count($post['attributes']['tags']['data']))
              <div class="mb-3">
                @foreach ($post['attributes']['tags']['data'] as $tag)
                  <span class="badge bg-success">{{ $tag['attributes']['name'] }}</span>
                @endforeach
              </div>
            @endif
            <p class="content">
              {{ $post['attributes']['content'] }}
            </p>

            <hr />
            <form action="/post/{{ $post['id'] }}" method="POST">
              @csrf
              <h2>Add Your Comment</h2>
              <div class="mb-3">
                <label for="email" class="form-label">Email address</label>
                <input type="email" class="form-control" id="email" name="email" required>
              </div>
              <div class="mb-3">
                <label for="content" class="form-label">Your Comment</label>
                <textarea rows="5" class="form-control" id="content" name="content" required></textarea>
              </div>
              <button type="submit" class="btn btn-primary">Submit</button>
            </form>
        </div>
    </body>
</html>

Enter fullscreen mode Exit fullscreen mode

This page just shows the post's details. Similar to how you extracted the post's data, the post's field is nested inside the attributes field.

This page also has a comments form at the end of it. You'll implement its functionality after this.

Finally, add the new route in routes/web.php:

Route::get('/post/{id}', [\App\Http\Controllers\BlogController::class, 'viewPost']);

Enter fullscreen mode Exit fullscreen mode

Now, open the home page again and click on Read More for one of the posts. A new page will open with the post's content.

Create Laravel Blog with Strapi v4

If you scroll down, you'll see a form to add your comment.

Create Laravel Blog with Strapi v4

Add Comment Functionality

The last thing you'll do in this tutorial is add the commenting functionality. The form is already added, so you just need to add the POST route to add the comment.

Add the following method in app/Http/Controllers/BlogController.php:

public function addComment (Request $request, $id) {
        $data = [
            "data" => [
                'email' => $request->get('email'),
                'content' => $request->get('content'),
                'post' => $id
            ]
        ];

        $response = Http::withToken(env('STRAPI_API_TOKEN'))->post(env('STRAPI_URL') . '/api/comments', $data);

        if ($response->failed()) {
            if (isset($data['error'])) {
                Log::error('Server error: ' . $data['error']['message']);
            } else {
                Log::error('Request Failed');
            }

            return response()->redirectTo('/');
        }

        //successfully added
        return response()->redirectTo('/post/' . $id);
    }
Enter fullscreen mode Exit fullscreen mode

You first format the data as Strapi likes it. When adding a content type entry, you should nest the data inside a data parameter. Here, you add the email, content, and post fields. Notice that we are skipping validation here for tutorial simplicity.

Then, you send a POST request to the endpoint /api/comments. Strapi's endpoint pattern for adding a content type entry is /api/{collection_name}. You pass the data as a second parameter to the post method.

If the request fails, the user is redirected to the home page. If it's successful, the user is redirected back to the post's page.

Next, add before the comment form in resources/views/post.blade.php the following:

<hr/>
@if (count($post['attributes']['comments']['data']))
    <div class="comments">
        <h2>Comments</h2>
        @foreach ($post['attributes']['comments']['data'] as $comment)
            <div class="card mb-3">
                <div class="card-body">
                    {{ $comment['attributes']['content'] }}
                </div>
                <div class="card-footer">
                    By {{ $comment['attributes']['email'] }}
                </div>
            </div>
        @endforeach
    </div>
@endif
Enter fullscreen mode Exit fullscreen mode

This will show the comments if a post has any.

Finally, add the new route in routes/web.php:

Route::post('/post/{id}', [\App\Http\Controllers\BlogController::class, 'addComment']);
Enter fullscreen mode Exit fullscreen mode

Let's test it out. Go to a post's page, then go to the comment form. Add your comment and click Submit. You'll be redirected back to the post's page, but you can see the comment below the post.

Create Laravel Blog with Strapi v4

Conclusion

In this tutorial, you learned how to build a blog with Laravel and Strapi. Strapi is completely customizable, and that simplifies the process of adding content types, adding entries, and using its endpoints to query the content types or add new entries in them.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .