Getting started with Laravel Blueprint

I recently discovered Laravel Blueprint, a code generation tool that creates multiple Laravel components (Models, Controller, Migrations etc) from a single yaml file.

What is Laravel Blueprint?

"Blueprint is an open-source tool for rapidly generating multiple Laravel components from a single, human readable definition."

What this means is you no longer have to manually generate files using php artisan ... commands and you can get straight into the deep code of your application.

If you've ever used the CMS Jekyll, you'll be familiar with the syntax, it's just a YAML file.

If you are unfamiliar with YAML syntax, all you need to know is that indentation is key, indent incorrectly and you'll have trouble. Watch those tabs!

Quick Blueprint commands

We'll dig into this later, but if you're here for a quick reference, these are the commands you need to know.

/* Generate a new draft.yaml file in a laravel project */
php artisan blueprint:new

/* Run blueprint to generate files based on your draft.yaml */
php artisan blueprint:build

/* Remove all the files generated by previous build command */
php artisan blueprint:erase

How to install Blueprint

If you've already got Laravel 10, it's easy to install Blueprint using a simple composer command.

composer require -W --dev laravel-shift/blueprint

php artisan blueprint:new

Optionally, you can also install the test package to run the generated unit tests.

composer require --dev jasonmccreary/laravel-test-assertions

And that's it for the installation steps!

Getting started

This is where you need a good idea of the structure of the project you're building, at the very least what Models you require, everything else can be added after.

You'll find in the root of your laravel installation a file called Draft.yml, this is where you'll specify your data structure.

We're going to start with our Models:

models:
  Post:
    title: string
    content: longtext
    author: string
    category: string
    published_at: nullable timestamp

  Comment:
    content: longtext
    author: string
    published_at: nullable timestamp

You can see here that all we're doing is an indented list of two Models, Post and Comment, and listing out the fields and field types.

This is actually enough for you to be able to run the blueprint build command and see what's generated.

php artisan blueprint:build

From just specifying those models, blueprint has generated our Model files, the migrations and some factories!

But this is just the basics, we're going to extend this in stages.

Relationships

For our basic blog we've got posts and comments, but we need those comments to be associated with a blog post, they can't just exist without a parent model.

We do this by adding a relationship value to each model, similar to how you would specify a relationship in your Model files.

/* draft.yaml */
models:
  Post:
    title: string
    content: longtext
    author: string
    category: string
    published_at: nullable timestamp
    relationships:
      hasMany: Comment

  Comment:
    content: longtext
    author: string
    post_id: id foreign
    published_at: nullable timestamp
    relationships:
      belongsTo: Post

We've just told blueprint that our two models have a relationship, Posts can have many Comments and a Comment must belong to a Post.

Notice that we have added the foreign id column post_id, this creates a foreign key constraint in the database.

Blueprint will assume you want to the foreign key to be from the posts table, if you wanted to specify a different table you could do that too with post_id: id foreign:other_posts_table

What if we extend for Authors too?

/* draft.yaml */
models:
  Post:
    title: string
    content: longtext
    author_id: id foreign
    category: string
    published_at: nullable timestamp
    relationships:
      hasMany: Comment

  Comment:
    content: longtext
    post_id: id foreign
    author_id: id foreign
    published_at: nullable timestamp
    relationships:
      belongsTo: Post

  Author:
    name: string
    relationships:
      hasMany: Post, Comment

Here you can see we've added multiple relationships simply by using a comma seperated list.

Now you can refresh your generated files with the new relationships by running in your command line:

php artisan blueprint:erase && php artisan blueprint:build

Look closely at the generated migration files to see how the relationships are created, you can adjust your yaml file and rebuild as many times as you like.

Seeders

If you want to get playing with some dummy data quickly, blueprint will even create seeders for you.

Just add the seeders you want to generate after your models in your draft.yaml file, but make sure there are no spaces or tabs before the section.

/* draft.yaml */
models:
  Post:
    title: string
    content: longtext
    author_id: id foreign
    category: string
    published_at: nullable timestamp
    relationships:
      hasMany: Comment

  Comment:
    content: longtext
    post_id: id foreign
    author_id: id foreign
    published_at: nullable timestamp
    relationships:
      belongsTo: Post

  Author:
    name: string
    relationships:
      hasMany: Post, Comment
  
seeders: Post, Comment, Author

Again refresh your generated files with the new seeders by running in your command line:

php artisan blueprint:erase && php artisan blueprint:build

Before you can start using the seeders, you need to run the database migrations as you would normally.

php artisan migrate && php artisan db:seed

If you use Tinker or Tinkerwell, you can also use these to test the seeders and see whats generated.

Controllers

If you plan on using blade templating it's a good idea to get Blueprint generating your controllers too, this is of course unnecessary if you are planning to use Livewire which as it's own Controller/View system.

/* draft.yaml */

controllers:
  Post:
    index:
      query: all
      render: post.index with:posts

Let's start with the basic index function, under your seeders you need a new section for controllers.

Here we are telling Blueprint for the model Post, we want an index function that runs fetch all posts query and returns a post.index blade file with the results of the query.

This may sound confusing at first, but when you look at the generated code, it's simply a standard function that returns all instances of the Post model to a view.

public function index()
{
    $posts = Post::all();
    return view('post.index', compact('posts'));
}

Let's take this further and create the whole Create, Read, Update and Delete process for the Post model.

controllers:
  Post:
    index:
      query: all
      render: post.index with:posts
    create:
      render: post.create
    store:
      validate: title, content, author
      save: post
      redirect: post.index
    show:
      render: post.show with:post
    edit:
      render: post.edit with:post
    update:
      validate: post
      update: post
      redirect: post.index
    destroy:
      delete: post
      redirect: post.index

You may also like