Cómo crear un blog con Laravel y Markdown

LaravelMarkdown

En este tutorial vamos a ver cómo crear un blog con Markdown en Laravel. Markdown es un lenguaje de marcado que te permitirá dar formato a cualquier texto mediante documentos de texto plano. En cuanto a Laravel, es el framework PHP más utilizado.

Usando archivos Markdown, podrás prescindir de una base de datos y redactar tus contenidos en archivos estáticos que podrás almacenar en GitHub junto con el código de tu web.

Introducción

Vamos a crear una sencilla aplicación con Laravel, en la que instalaremos Tailwind como framework CSS y también Markdown, así como las librerías necesarias para resaltar la sintaxis del código que agregues.

Antes de continuar, es recomendable que tengas ciertos conocimientos previos de HTML, PHP y CSS. Para ello puedes consultar las siguientes guías:

También necesitarás tener ciertos conocimientos básicos acerca de la línea de comandos. Si no los tienes, consulta el siguiente tutorial:

Si vas a crear un blog con Markdown, también deberías conocer este lenguaje. Si nunca lo has utilizado, puedes consultar el tutorial de Markdown. Realmente, no tardarás más que unos minutos en aprender su sintaxis básica.

Puedes consultar el código de la aplicación que vamos a crear en GitHub. Un ejemplo del posible resultado podría ser mi blog edulazaro.com, ya que he usado este mismo tutorial como base del mismo.

Instalación de Laravel

Antes de instalar Laravel, tendrás que asegurarte de que tienes composer instalado en tu sistema, así como PHP. Si no tienes PHP instalado en tu sistema, consulta el tutorial de instalación de PHP en Windows, en donde verás cómo instalar PHP tanto por separado como usando paquetes como Wamp, XAMPP o MAMP.

Crea un proyecto con Laravel

Para crear un proyecto con Laravel, abre una ventana de línea de comandos y sigue estos pasos:

  1. Sitúate en el directorio en el que quieres crear el proyecto y ejecuta el comando create-project, reemplazando mi-proyecto por el nombre del proyecto que quieres crear:
    composer create-project laravel/laravel mi-proyecto
  2. Seguidamente, accede al proyecto usando el comando cd:
    cd nuevo-proyecto
  3. Ahora ya podrás iniciar el servidor local mediante el siguiente comando:
    php artisan serve
  4. Con esto ya habrás creado el proyecto.

A continuación vamos a instalar un framework CSS. Podrás usar Tailwind, Bootstrap o el que prefieras.

Instala Tailwind en Laravel

Dado que en casi todos los proyecto de esta web he usado Bootstrap, en esta ocasión instalaremos Tailwind. De todos modos, si prefieres usar Bootstrap, siempre puedes consultar el tutorial de introducción a Bootstrap.

Existen varias stacks de Laravel que ya traen Tailwind instalado por defecto, como Laravel Breeze. Sin embargo, dado que estamos creando el proyecto desde cero, instalaremos Tailwind manualmente.

Ya hemos visto cómo instalar Tailwind CSS en Laravel de diferentes formas, así que nos decantaremos por el método más rápido. Sin embargo, necesitarás tener instalado tanto el entorno Node.js como el gestor de paquetes npm antes de continuar. Si no tienes estas herramientas instaladas, consulta el tutorial de instalación de Node.js.

Para instalar Tailwind en Laravel sigue estos pasos:

  1. Abre una ventana de línea de comandos y sitúate en el directorio raíz del proyecto.
  2. Luego instala tanto Tailwind como sus dependencias mediante el comando npm install:
    npm install -D tailwindcss postcss autoprefixer
  3. Seguidamente, ejecuta el comando que ves a continuación para generar el archivo de configuración de Tailwind y el de PostCSS en el directorio raíz:
    npx tailwindcss init -p
  4. Ahora edita el archivo tailwind.config.js creado en el paso anterior y agrega las rutas hacia los archivos con extensión .blade de Laravel, así como hacia los posibles archivos .js o .vue:
    /** @type {import('tailwindcss').Config} */
    module.exports = {
      content: [
        "./resources/**/*.blade.php",
        "./resources/**/*.js",
        "./resources/**/*.vue",
      ],
      theme: {
        extend: {},
      },
      plugins: [],
    }
  5. Finalmente, edita el archivo /resources/css/app.css y agrega las directicas de Tailwind en la parte superior del archivo:
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  6. Y con esto ya hemos configurado Tailwind.

En este tutorial no usaremos código CSS, por hacerlo más sencillo, ya que la idea es que no tengamos que incluir código CSS en ningún archivo gracias a Tailwind. Sin embargo, esto no siempre es posible. Cuando necesito agregar código CSS personalizado, entonces opto por usar Saas, por lo que en lugar de configurar el archivo /resources/css/app.css tendríamos que configurar el archivo /resources/sass/app.scss.

Si prefieres usar Sass, consulta la sección correspondiente del tutorial en donde explico cómo configurar Laravel con Tailwind y Sass. En resumen, bastará con que edites el archivo webpack.mix.js y reemplaces la línea .postCss('resources/sass/app.scss', 'public/css'); por el siguiente bloque de código:

.sass('resources/sass/app.scss', 'public/css')
    .options({
        processCssUrls: false,
        postCss: [tailwindcss('./tailwind.config.js')],
    })
    .version();

Si quieres que tus artículos tengan algún estilo por defecto, ya sea para los títulos como para las listas o las negritas o cursivas, también puedes usar el plugin typography de Tailwind ejecutando este comando:

npm install -D @tailwindcss/typography

Luego edita el archivo tailwind.config.js y agrega el plugin anterior en la sección de plugins:

plugins: [
    require('@tailwindcss/typography'),
    // ...
],

A continuación vamos a configurar Markdown con Laravel.

Configura Markdown en Laravel

En este apartado veremos cómo usar archivos con extensión .mdx y cómo configurar una directiva que nos permita incluir código Markdown en nuestros archivos .blade. Para ello usaremos el paquete graham-campbell/markdown, creado por Graham Campbell.

Para instalar y configurar Markdown en Laravel sigue los pasos que ves a continuación:

  1. Abre una ventana de terminal y ejecuta el siguiente comando:
    composer require graham-campbell/markdown
  2. Luego, por conveniencia, edita el archivo config/app.php y agrega la Facade de Markdown en el array de aliases que encontrarás al final del mismo:
    'aliases' => [
        ...
        'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
    ],
  3. Para personalizar la configuración de Markdown ejecuta le siguiente comando para generar el archivo config/markdown.php:
    php artisan vendor:publish
  4. Si aparece un listado, selecciona la opción Provider: GrahamCampbell\Markdown\MarkdownServiceProvider. En su interior podrás ver varias opciones de configuración y para qué sirve cada una de ellas.

Con esto ya habremos configurado Markdown, pudiendo crear vistas con la extensión .md, .md.php, y .md.blade.php. También podremos usar la directiva @markdown de blade para insertar un bloque con código Markdown.

Configuración inicial del proyecto

Lo primero que vamos a hacer es definir las rutas y controladores del blog, así como la plantilla que usaremos tanto para el índice como para cada entrada.

Crea de las rutas del blog

Necesitamos definir la estructura de rutas del proyecto. Para ello usaremos el archivo routes/web.php, que tendrás que editar. Debes agregar estas rutas:

Route::get('/', function () { return view('home'); })->name('home');

Route::get('/blog', [App\Http\Controllers\BlogController::class, 'index'])->name('blog.index');
Route::get('/blog/{slug}', [App\Http\Controllers\BlogController::class, 'show'])->name('blog.show');

Usaremos la ruta home para mostrar una vista con la página de inicio. Seguidamente hemos declarado una ruta llamada blog.index para mostrar el índice del blog y otra para los artículos individuales, a la que le hemos dado el nombre de blog.show. En esta última ruta hemos definido el parámetro slug, que será la URI de cada entra del blog.

Tal y como vemos, hemos hecho referencia a la clase BlogController, que no es otra cosa que el controlador que usaremos para le blog, que todavía no hemos creado.

Crea del controlador del blog

Vamos a crear un controlador que gestione tanto el índice como los artículos del blog. Para ello, ejecuta el siguiente comando desde la terminal de comandos:

php artisan make:controller BlogController

Esto creará un controlador básico en el directorio app/Http/Controllers/ con la siguiente clase.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class BlogController extends Controller
{
    //
}

A continuación define los métodos index y show, que son los métodos que serán invocados desde las rutas que hemos definido

/**
  * Muestra el índice del blog
  *
  * @return \Illuminate\Contracts\Support\Renderable
  */
  public function index(Request $request)
  {
      //
  }

/**
 * Muestra una entrada del blog
 *
 * @return \Illuminate\Contracts\Support\Renderable
 */
 public function show(Request $request)
 {
     //
 }

Dejaremos el código de cada método para más adelante.

Crea de la plantilla principal

Vamos a crear un plantilla básica que contenga un menú y un pie de página, que usaremos tanto en la página principal del blog como en los artículos. No es el objetivo de este tutorial explicar el uso de Tailwind, así que nos limitaremos a incluir las clases de este framework sin entrar en detalles.

Para crear la plantilla sigue los pasos que ves a continuación:

  1. Crea el archivo /resources/views/layouts/main.blade.php.
  2. Luego agrega el siguiente código en el interior del mismo:
    <!DOCTYPE html>
    <html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
    
            <title>@yield('title', 'Edu Lazaro')</title>
    
            @hasSection('description')
                <meta name="description" content="@yield('description')">
            @endif
    
            <!-- Fonts -->
            <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
    
            <!-- Styles -->
            <link rel="stylesheet" href="{{ mix('css/app.css') }}">
        </head>
        <body class="w-full">
            <header class="z-50 flex-none text-sm font-semibold max-w-screen-2xl  mx-auto px-4 sm:px-6 lg:px-8">
                <nav>
                    <div class="relative flex items-center py-[2.125rem] text-base">
                        <a class="mr-auto flex-none" href="{{ route('home') }}">Edu Lazaro</a>
                        <div class="lg:flex lg:items-center">
                            <a class="ml-8" href="{{ route('home') }}">Home</a>
                            <a class="ml-8" href="{{ route('blog') }}">Blog</a>
                        </div>
                    </div>
                </nav>
            </header>
            <main class="mt-8 max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8">
                <div class="container ">
                    @yield('content')
                </div>
            </main>
        </body>
    </html>
  3. Con esto ya habremos creado la plantilla o layout principal.

Lo que hemos hecho ha sido agregar el head con un campo el título y otro para la meta descripción de las entradas, algo imprescindible para el posicionamiento de tu blog. Para ello hemos usado la directiva @yield. También hemos agregado la cabecera con un menú básico, así como un contenedor para el contenido.

Cuando creemos una entrada de blog, extenderá de esta plantilla, insertando el contenido de las entradas en el lugar en el que hemos definido el campo content mediante la directiva @yield('content').

Diseño e implementación del blog

Aquí es cuando se empieza  poner la cosa interesante. Primero veremos cuál es el mejor modo de mostrar las entradas individuales del blog y seguidamente cómo podemos crear un índice que liste las entradas.

Implementación de las entradas del blog

Lo primero que vamos a hacer es implementar el método show del controlador BlogController. En este método primero leeremos el valor del parámetro slug de la ruta que hemos declarado antes. Luego comprobaremos si existe una vista con este nombre en el directorio resources/views/blog/entries/. Si existe, mostraremos la entrada. En caso contrario, mostraremos un error 404 mediante el método abort. Este es el código completo del método show:

$routeParams = request()->route()->parameters();

if (!isset($routeParams['slug'])) abort(404);

$viewName = 'blog.entries.' . $routeParams['slug'];

if (!view()->exists($viewName)) abort(404);

return view('blog.show',
    [
        'viewName' => $viewName,
    ]
);

Lo que hemos hecho es comprobar si el nombre de la vista existe. Si es así, pasamos el nombre del archivo a la vista blog.show. Seguramente te estés preguntando que por qué no mostramos la vista con la entrada directamente. La respuesta es que no funciona, o funciona a medias. Si fueses a cargar una vista con extensión .blade.php y en su interior usares la sentencia @markdown para incluir código Markdown, sí funcionaría:

@markdown
...
@endmarkdown

Sin embargo, en este caso vamos a cargar un archivo con extensión .md.blade.php, en cuyo caso, el HTML o las directivas de blade que usemos no funcionarán salvo que carguemos una vista normal y luego en su interior incluyamos la vista con nuestro artículo de blog, que ya estaría en un archivo .md.blade.php. Para verlo de un modo más claro, a continuación tienes el contenido del archivo resources/views/blog/show.blade.php:

@extends('layouts.main')

@section('content')
    <div class="prose max-w-none">
        @include($viewName)
    </div>

    <div class="bg-sky-200 p-3 mt-4 mb-4">
        <strong>Importante!</strong> Recuerda suscribirte a este blog!
    </div>
@endsection

Lo primero que hemos hecho es decirle a la vista que use la plantilla layouts.main. Luego definimos el contenido de la sección content de la plantilla. En su interior incluimos dinámicamente la vista con nuestro artículo, que todavía no hemos creado. Para ello usamos la sentencia @include, a la que la pasamos el nombre de la vista que contiene nuestra entrada. Tal y como ves, hemos agregado la clase prose de Tailwind, que agregará estilos básicos por defecto a los textos de nuestros artículos.

Para demostrar el uso de HTML, también hemos agregado un pequeño bloque con un mensaje.

Para finalizar, vamos a crear un artículo de ejemplo. Para ello crea el archivo bienvenido.md.blade.php en el directorio resources/views/blog/entries. El nombre del archivo será equivalente a la slug del artículo, por lo que nuestro artículo se encontraría en la URL /blog/bienvenido. Este es el contenido del artículo loremipsumizado:

@section('title', 'Lorem Ipsum passage')
@section('description', 'Descripción del Lorem Ipsum')
@section('published', '2022-08-15')

# **Lorem Ipsum**
   
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

## Lorem ipsum 1.1
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.:

1. Lorem ipsum **dolor** sit amet
2. Lorem ipsum **dolor** sit amet

Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse **quam nihil** molestiae consequatur.

```
@verbatim
// Código de ejemplo
$test = 4;
@endverbatim
```

<button class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded">
Botón de ejemplo
</button>

## Lorem ipsum 1.2
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.

En la parte superior hemos definido el título y la descripción del la entrada, que se mostrarán en la zona de la plantilla en donde hemos definido estos elementos. También he agregado la fecha de publicación para poder ordenar los artículos. Luego hemos incluido el artículo en formato Markdown. Además también hemos agregado un botón de ejemplo, demostrando cómo incluir HTML en el archivo.

Para ver la entrada del blog, accede a la ruta /blog/bienvenido desde tu navegador. Deberías ver algo así:

A continuación veremos cómo crear una lista con las entradas del blog.

Implementación del índice del blog

En este apartado vamos a ver cómo crear un índice para el blog que liste los artículos. Para ello partimos de una lista de archivos que tenemos en el directorio resources/views/blog/entries. Por ahora solamente tenemos una entrada, así que vamos a agregar también otras dos a las que daré el nombre de tutorial-1.md.blade.php y tutorial-2.mb.blade.php.

En el listado de de entradas debemos mostrar una lista de artículos con su título, su URL y una breve descripción. Sin embargo, solamente conocemos la URL, que se forma a partir del nombre que le damos al archivo. Si queremos obtener el título y la descripción, no nos quedará otra que obtenerlo del interior de los artículos.

A continuación tienes el código de método index del controlador BlogController:

public function index()
{
    $entries = [];
    $filePaths  = glob(resource_path() . '/views/blog/entries/*.php');
    foreach ($filePaths as $filePach) {
        if (!is_file($filePach)) continue;
            
        $entry = [];
        $fileName = basename($filePach, '.md.blade.php');
        $viewName = 'blog.entries.' . $fileName;

        $sections = view($viewName)->renderSections();

        $entry['slug'] = $fileName;
        $entry['title'] = !empty($sections['title']) ? $sections['title'] : '';
        $entry['published'] = !empty($sections['published']) ? $sections['published'] : '';
        $entry['description'] = !empty($sections['description']) ? $sections['description']: $sections['title'];
        $entries[] = $entry;
    }

    uasort($entries, function($a, $b) {
        return $a['published'] < $b['published'];
    });

    return view('blog.index', ['entries' => $entries]);
}

Lo que hemos hecho ha sido leer los archivos del directorio resources/views/blog/entries, obteniendo tanto el nombre del archivo como las opciones title, description y published de su interior. Para lo primero hemos usado la función glob de PHP, mientras que para lo segundo hemos obtenido la vista deseada para luego usar el método renderSections, obteniendo cada uno de los elementos que nos interesan del array resultante.

Nuestro objetivo ha sido crear el array entries, que contiene el título, la descripción y la fecha de cada publicación. Para ordenar el array en función de la fecha de publicación de los artículos hemos usado la función usort de PHP. Finalmente, una vez, están los artículos ordenados, pasamos a la vista blog.index el array resultante.

A continuación puedes ver el código de la vista resources/views/blog/index.blade.php, en la que sencillamente mostramos un listado de entradas:

@extends('layouts.main')

@section('title', 'Lista de entradas')
@section('description', 'Lista de entradas del blog')

@section('content')
    <h1>Lista de entradas</h1>
    <ul class="list-disc list-inside mt-8">
         @foreach ($entries as $entry)
        <li class="block mt-4">
            <a href="{{ route('blog.show', ['slug' => $entry['slug']]) }}" alt="{{ $entry['title'] }}">{{ $entry['title'] }}</a>
        </li>
        @endforeach
    </ul>
@endsection

Tras guardar tanto el controlador como la vista, ya puedes acceder a la ruta /blog de tu aplicación desde tu navegador. Deberías poder ver un listado de artículos:

A modo de mejora, podría resultar interesante almacenar el caché el array con el listado de artículos, de modo que solamente lo refresquemos cada vez que creemos un nuevo artículo. Para ello, agrega la siguiente línea en la parte superior del controlador BlogController:

use Illuminate\Support\Facades\Cache;

Luego modifica el método index tal que así:

public function index()
{
    $entries = Cache::store('file')->get('blog.entries');

    if ($entries) return view('blog.index', ['entries' => $entries]);

    $entries = [];
    $filePaths  = glob(resource_path() . '/views/blog/entries/*.php');
    foreach ($filePaths as $filePach) {
        if (!is_file($filePach)) continue;
            
        $entry = [];
        $fileName = basename($filePach, '.md.blade.php');
        $viewName = 'blog.entries.' . $fileName;

        $sections = view($viewName)->renderSections();

        $entry['slug'] = $fileName;
        $entry['title'] = !empty($sections['title']) ? $sections['title'] : '';
        $entry['published'] = !empty($sections['published']) ? $sections['published'] : '';
        $entry['description'] = !empty($sections['description']) ? $sections['description']: $sections['title'];
        $entries[] = $entry;
    }

    uasort($entries, function($a, $b) {
        return $a['published'] < $b['published'];
    });

    Cache::store('file')->put('blog.entries', $entries);
    return view('blog.index', ['entries' => $entries]);
}

Lo que hemos hecho ha sido almacenar en caché el array $entries bajo la clave blog.entries mediante esta sentencia:

Cache::store('file')->put('blog.entries', $entries);

Luego, en la parte superior del método, obtenemos el array que contiene los artículos desde la caché y, si existe, cargamos directamente la vista, ahorrándonos el proceso de lectura de los artículos:

$entries = Cache::store('file')->get('blog.entries');
if ($entries) return view('blog.index', ['entries' => $entries]);

Eso sí, cada vez que crees un nuevo artículo o actualices su título o su descripción, tendrás que acordarte de vaciar la caché desde la terminal:

php artisan cache:clear

Y con esto ya hemos terminado.

Finalizando

Si creas un blog usando este repositorio como base, puedes compartirlo en la sección de comentarios. Por ejemplo, he tomado esta estructura como base para el mío. Existen más paquetes con los que podrás crear contenido en Laravel usando la sintaxis Markdown. Consulta la guía en donde explico cómo usar Markdown con Laravel para más información

Recuerda que siempre puedes consultar el código de la aplicación en GitHub.

Espero que este artículo te haya resultado útil .


Avatar de Edu Lazaro

Edu Lázaro: Ingeniero técnico en informática, actualmente trabajo como desarrollador web y programador de videojuegos.

👋 Hola! Soy Edu, me encanta crear cosas y he redactado esta guía. Si te ha resultado útil, el mayor favor que me podrías hacer es el de compatirla en Twitter 😊

Si quieres conocer mis proyectos, sígueme en Twitter.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

“- Hey, Doc. No tenemos suficiente carretera para ir a 140/h km. - ¿Carretera? A donde vamos, no necesitaremos carreteras.”