Building a Rest API Backend with Laravel 8 and JSON:API

In this article, we will create a Web API service for rating dishes at restaurants.

To learn more details the recommended Laravel domain for local development package, we recommend checking out the legacy Laravel JSON:API tutorial.

Let’s call our web app “Ratify”…

Models

$ php artisan make:model Restaurant --migrationModel created successfully. Created Migration: 2022_01_20_122048_create_restaurants_table
class CreateRestaurantsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('restaurants', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('restaurants');
}
}
Schema::create('restaurants', function (Blueprint $table) {
$table->id();
+ $table->string('name');
+ $table->string('address');
$table->timestamps();
});
$ php artisan migrate
Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (225.35ms) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (169.18ms) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (128.76ms) Migrating: 2022_01_20_122048_create_restaurants_table Migrated: 2022_01_20_122048_create_restaurants_table (73.07ms)
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Restaurant extends Model
{
use HasFactory;
}
class Restaurant extends Model
{
use HasFactory;
+
+ protected $fillable = ['name', 'address'];
}
$ php artisan make:model Dish --migration
class Dish extends Model
{
use HasFactory;
+
+ protected $fillable = ['name', 'rating', 'restaurant_id'];
}
php artisan migrate
class Restaurant extends Model
{
use HasFactory;

protected $fillable = ['name', 'address'];
+
+ public function dishes()
+ {
+ return $this->hasMany('App\Models\Dish');
+ }
}
class Dish extends Model
{
protected $fillable = ['name', 'rating'];
+
+ public function restaurant()
+ {
+ return $this->belongsTo('App\Models\Restaurant');
+ }
}
php artisan make:seeder RestaurantSeeder
use Illuminate\Database\Seeder;
+use App\Models\Restaurant;

class RestaurantSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
+ $sushiPlace = Restaurant::create(['name' => 'Sushi Place', 'address' => '123 Main Street']);
+ $burgerPlace = Restaurant::create(['name' => 'Burger Place', 'address' => '456 Other Street']);
+
+ $sushiPlace->dishes()->createMany([
+ ['name' => 'Volcano Roll', 'rating' => 3],
+ ['name' => 'Salmon Nigiri', 'rating' => 4],
+ ]);
+
+ $burgerPlace->dishes()->createMany([
+ ['name' => 'Barbecue Burger', 'rating' => 5],
+ ['name' => 'Slider', 'rating' => 3],
+ ]);
}
}
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
- // User::factory(10)->create();
+ $this->call(RestaurantSeeder::class);
}
}
$ php artisan db:seed

Setting Up the Web Service

$ composer require cloudcreativity/laravel-json-api
$ composer require --dev cloudcreativity/json-api-testing
protected function mapApiRoutes()
{
- Route::prefix('api')
- ->middleware('api')
+ Route::middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
$ php artisan make:json-api
'resources' => [
- 'posts' => \App\Post::class,
+ 'restaurants' => \App\Models\Restaurant::class,
+ 'dishes' => \App\Models\Dish::class,
],
$ php artisan make:json-api:schema Restaurants
$ php artisan make:json-api:schema Dishes
public function getAttributes($resource)
{
return [
+ 'name' => $resource->name,
+ 'address' => $resource->address,
'created-at' => $resource->created_at,
'updated-at' => $resource->updated_at,
];
}
public function getRelationships($resource, $isPrimary, array $includeRelationships)
{
return [
'dishes' => [
self::SHOW_SELF => true,
self::SHOW_RELATED => true,
]
];
}
class Schema extends SchemaProvider
{
...
public function getAttributes($resource)
{
return [
+ 'name' => $resource->name,
+ 'rating' => $resource->rating,
'created-at' => $resource->created_at,
'updated-at' => $resource->updated_at,
];
}
+
+ public function getRelationships($resource, $isPrimary, array $includeRelationships)
+ {
+ return [
+ 'restaurant' => [
+ self::SHOW_SELF => true,
+ self::SHOW_RELATED => true,
+ ]
+ ];
+ }
}
$ php artisan make:json-api:adapter Restaurants
$ php artisan make:json-api:adapter Dishes
public function __construct(StandardStrategy $paging)
{
- parent::__construct(new \App\Restaurant(), $paging);
+ parent::__construct(new \App\Models\Restaurant(), $paging);
}
protected function dishes()
{
return $this->hasMany();
}
public function __construct(StandardStrategy $paging)
{
- parent::__construct(new \App\Dish(), $paging);
+ parent::__construct(new \App\Models\Dish(), $paging);
}
...
+protected function restaurant()
+{
+ return $this->hasOne();
+}
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
JsonApi::register('default')->routes(function ($api) {
$api->resource('restaurants')->relationships(function ($relations) {
$relations->hasMany('dishes');
});
$api->resource('dishes')->relationships(function ($relations) {
$relations->hasOne('restaurant');
});
});

Trying It Out

{
"data": {
"type": "restaurants",
"id": "1",
"attributes": {
"name": "Sushi Place",
"address": "123 Main Street",
"createdAt": "2020-09-20T12:29:15.000000Z",
"updatedAt": "2020-09-20T12:29:15.000000Z"
},
"relationships": {
"dishes": {
"links": {
"self": "http://localhost/api/v1/restaurants/1/relationships/dishes",
"related": "http://localhost/api/v1/restaurants/1/dishes"
}
}
},
"links": {
"self": "http://localhost/api/v1/restaurants/1"
}
}
}
{
"data": [
{
"type": "dishes",
"id": "1",
"attributes": {
"name": "Volcano Roll",
"rating": 3,
"createdAt": "2020-09-20T14:48:45.000000Z",
"updatedAt": "2020-09-20T14:48:45.000000Z"
},
"relationships": {
"restaurant": {
"links": {
"self": "http://localhost/api/v1/dishes/1/relationships/restaurant",
"related": "http://localhost/api/v1/dishes/1/restaurant"
}
}
},
"links": {
"self": "http://localhost/api/v1/dishes/1"
}
},
{
"type": "dishes",
"id": "2",
"attributes": {
"name": "Salmon Nigiri",
"rating": 4,
"createdAt": "2020-09-20T14:48:45.000000Z",
"updatedAt": "2020-09-20T14:48:45.000000Z"
},
"relationships": {
"restaurant": {
"links": {
"self": "http://localhost/api/v1/dishes/2/relationships/restaurant",
"related": "http://localhost/api/v1/dishes/2/restaurant"
}
}
},
"links": {
"self": "http://localhost/api/v1/dishes/2"
}
}
]
}
{
"data": {
"type": "restaurants",
"attributes": {
"name": "Spaghetti Place",
"address": "789 Third Street"
}
}
}
{
"data": {
"type": "restaurants",
"id": "3",
"attributes": {
"name": "Spaghetti Place",
"address": "789 Third Street",
"createdAt": "2020-09-20T14:52:03.000000Z",
"updatedAt": "2020-09-20T14:52:03.000000Z"
},
"relationships": {
"dishes": {
"links": {
"self": "http://localhost/api/v1/restaurants/3/relationships/dishes",
"related": "http://localhost/api/v1/restaurants/3/dishes"
}
}
},
"links": {
"self": "http://localhost/api/v1/restaurants/3"
}
}
}

There’s More

To learn more, check out the Laravel JSON API Docs.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store