Laravel makes it quick to get up and running with Rest APIs that you can use to feed data to mobile apps, javascript applications or other applications. In this post, I'll show you how easy it is to do it.

To make it simple, let's create an API that only delivers content about books and their authors. We're not going to add "create" and "update" functionality for now.

Setup

We can start by creating a new Laravel application. In your terminal execute the command: laravel new books-api. If you don't have Laravel installed globally, you can use Composer:

composer create-project --prefer-dist laravel/laravel books-api
Keep in mind that by the time of this article, I'm using Laravel version 7.10.3

That will create a new folder books-api, you can go inside and edit the .env file. We need to setup the database access for the application:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<YOUR-DB-NAME>
DB_USERNAME=<YOUR-DB-USER>
DB_PASSWORD=<YOUR-DB-USER-PASSWORD>

To make sure it connects correctly, you can run php artisan migrate. The application already ships with database migrations for the User model. If no errors popped up, you're good to go.

Models, Migrations and Controllers

We will need at least a model for books and one for authors, so let's create them using Artisan's command: php artisan make:model Author -mcr.

This command should create three files:

  • app/Author.php : That's our model
  • app/Http/Controllers/AuthorController.php : A resource controller (created with the help of the cr flag)
  • and database/migrations/XXXX_create_authors_table.php: That's the database migration created with the help of the m flag in the command.

Let's start with the migration. Go to the newly created file and add the following code:

public function up()
{
    Schema::create('authors', function (Blueprint $table) {
        $table->id();
        $table->string('name'); // we add this line here
        $table->timestamps();
    });
}

Whatever fields you add here will be added to the authors table in the database. To learn what are the available methods, check out Laravel's documentaton.

Now let's do the same for books: php artisan make:model Book -mcr.

Then go to database/migrations/XXXXX_create_books_table.php and add the following fields inside the up function:

public function up()
{
    Schema::create('books', function (Blueprint $table) {
        $table->id();
        $table->foreignId('author_id');
        $table->string('title');
        $table->decimal('price', 6, 2)->nullable();
        $table->timestamps();

        $table->foreign('author_id')
            ->references('id')
            ->on('authors')
            ->onDelete('cascade');
    });
}

Here we are also adding a reference to the author who wrote the book using a foreign key. After you add any other fields you want, you run the migrations with: php artisan migrate. If you got no errors in the console, good job! Now let's keep going.

We also need to create a relationship between authors and books in the models:

app/Author:

class Author extends Model
{
    public function books()
    {
        return $this->hasMany('App\Book');
    }
}

app/Book:

class Book extends Model
{
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}

With these, we can call the relationships as properties from each model:

$book = Book::first(); // Book instance
$book->author; // Author instance

$author = Author::first(); // Author instance
$author->books; // Collection of Books

If you call them using the "method" form, it will return you a QueryBuilder instance, and you can use to chain other methods, like:

$author = Author::first(); // Author instance
$author->books()
	->where('published', true)
    ->get(); // Collection of Books

Factories and Seeders

Now that we have the database setup, we need some data to test the API. In the real world, either you will already have the data, or maybe you can create it manually. I like to using factories and seeders, because it's less work and it's useful for automated testing. Let's create a factory for authors and for books:

php artisan make:factory AuthorFactory -m Author
Factory created successfully.

php artisan make:factory BookFactory -m Book
Factory created successfully.

Now if you go to the database/factories folder, you will see both files created.

In AuthorFactory, let's use the faker library to create random Author data:

$factory->define(Author::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
    ];
});

And we do the same for books, with the catch that it will receive the author_id as a parameter:

$factory->define(Book::class, function (Faker $faker, $author_id) {
    return [
        'author_id' => $author_id,
        'title' => $faker->sentence,
        'price' => $faker->randomFloat(2, 5, 100)
    ];
});

Now, we create a seeder for Authors:

php artisan make:seeder AuthorTableSeeder
Seeder created successfully.

In that seeder (which is in database/seeders/AuthorTableSeeder) we create 5 authors and 5 books for each author using the followinf code:

public function run()
    {
        factory(App\Author::class, 5)->create()
            ->each(function ($author) {
                factory(App\Book::class, 5)->create([
                    'author_id' => $author->id
                ]);
            });
    }

It calls the factory for the Author model and for each one created, it creates 5 books using basically the same syntax. Now, to finish it up, we call this seeder in the DatabaseSeeder class:

public function run()
    {
        $this->call(AuthorTableSeeder::class);
    }

With all migrations, factories and seeders in place, we run php artisan migrate --seed. You should see a list of all the migrations and seeders run.

Routes

We need routes to access the data, so we can go to routes/api.php and add them there. Routes added to that file will already have a /api prepended to the route, so keep that in mind. I want to see a list of authors, a list of books and some details of a specific book. So, I can start by adding those three routes at the end of the file:

Route::resource('authors', 'AuthorController')->only('index');
Route::resource('books', 'BookController')->only(['index', 'show']);

Now, since we created a resource controller in the beginning of this tutorial, we can now use the Route's resource static method. You pass as arguments the name of the route and a controller to map each of these seven routes:

  • Index
  • Create
  • Store
  • Show
  • Edit
  • Update
  • Delete

If you go to the controllers, you will see that all of those methods are in the there. Since we're developing a Rest API, we would not even need a route for create and edit, because those are meant to return a view with the forms for creating and editing the resource, respectively. As stated before we only need a list of authors, a list of books, and details of a specific book, so we use the  only() method to limit the resource routes.

Of course, we didn't need all of that boiler plate code, so might as well create each of those routes and methods from scratch, but I wanted to show you these methotds, because they save you time in your day-to-day.

Now, let's implement index() in the AuthorController, so go to app/Http/Controllers/AuthorController.php and add this code:

public function index()
{
    $authors = Author::all();

    return response()->json($authors);
}

Since we only have 5 authors (created by the seeder, remember?), we would not even need pagination, so let's query all the authors.

In BookController we need to implement index() for the list of books and show() for the details of the specific book:

public function index()
{
    $books = Book::with('author')->paginate(10);

    return response()->json($books);
}

public function show(Book $book)
{
    return response()->json($book);
}

The with() queries the relationship and already returns a book with its author.

The string you pass to this method must be equal to the name of the method you gave to the relationship in the Book model.

Since we have at least 25 books, let's create a pagination query 10 books every time.

In the case of show(), Laravel already resolves the book id passed to the route and gives us the Book instance as an argument.

Now if you run Laravel's server using php artisan serve, you can access http://localhost:8000/api/authors or http://localhost:8000/api/books in your browser or in app like Postman or Insomnia and you will get a JSON response like this:

{
  "current_page": 1,
  "data": [
    {
      "id": 1,
      "author_id": 6,
      "title": "Inventore voluptatem iure veniam quis nesciunt aut.",
      "price": "42.19",
      "created_at": "2020-05-16T17:39:22.000000Z",
      "updated_at": "2020-05-16T17:39:22.000000Z",
      "author": {
        "id": 6,
        "name": "Brandyn Gorczany",
        "created_at": "2020-05-16T17:39:22.000000Z",
        "updated_at": "2020-05-16T17:39:22.000000Z"
      }
    },
    ...
  ],
  "first_page_url": "http:\/\/localhost:8000\/api\/books?page=1",
  "from": 1,
  "last_page": 3,
  "last_page_url": "http:\/\/localhost:8000\/api\/books?page=3",
  "next_page_url": "http:\/\/localhost:8000\/api\/books?page=2",
  "path": "http:\/\/localhost:8000\/api\/books",
  "per_page": 10,
  "prev_page_url": null,
  "to": 10,
  "total": 25
}

If you want to access details of a book,  just go to http://localhost:8000/api/books/1 where "1" is the id of the book you want to access. This will return this response:

{
  "id": 1,
  "author_id": 6,
  "title": "Inventore voluptatem iure veniam quis nesciunt aut.",
  "price": "42.19",
  "created_at": "2020-05-16T17:39:22.000000Z",
  "updated_at": "2020-05-16T17:39:22.000000Z"
}

So, there you have it. It doesn't take too much time to create a Rest API with Laravel, as a bonus you already have factories and seeders setup to use in automated tests.

Stay tuned because in the next posts you'll learn how to authenticate in the API using Laravel Passport and how to use Eloquent's API Resources to fine tune what you want to display in each route.