In the previous part of this series, we initialized the posts resource and started building the frontend of the CMS. We designed the front page that shows all the posts and the single post page using Laravel’s templating engine, Blade.
In this part of the series, we will start building the API for the application. We will create an API for CRUD operations that an admin will perform on posts and we will test the endpoints using Postman.
The source code for this project is available here on GitHub.
To follow along with this series, a few things are required:
The Laravel framework makes it very easy to build APIs. It has an API resources feature that we can easily adopt in our project. You can think of API resources as a transformation layer between Eloquent models and the JSON responses that will be sent back by our API.
Since we are going to be performing CRUD operations on the posts in the application, we have to explicitly specify that it’s permitted for some fields to be mass-assigned data. For security reasons, Laravel prevents mass assignment of data to model fields by default.
Open the Post.php
file and include this line of code:
1// File: ./app/Post.php 2 protected $fillable = ['user_id', 'title', 'body', 'image'];
We will use the apiResource()
method to generate only API routes. Open the routes/api.php
file and add the following code:
1// File: ./routes/api.php 2 Route::apiResource('posts', 'PostController');
Because we will be handling the API requests on the
/posts
URL using thePostController
, we will have to include some additional action methods in our post controller.
At the beginning of this section, we already talked about what Laravel’s API resources are. Here, we create a resource class for our Post
model. This will enable us to retrieve Post
data and return formatted JSON format.
To create a resource class for our Post
model run the following command in your terminal:
$ php artisan make:resource PostResource
A new PostResource.php
file will be available in the app/Http/Resources
directory of our application. Open up the PostResource.php
file and replace the toArray()
method with the following:
1// File: ./app/Http/Resources/PostResource.php 2 public function toArray($request) 3 { 4 return [ 5 'id' => $this->id, 6 'title' => $this->title, 7 'body' => $this->body, 8 'image' => $this->image, 9 'created_at' => (string) $this->created_at, 10 'updated_at' => (string) $this->updated_at, 11 ]; 12 }
The job of this toArray()
method is to convert our P``ost
resource into an array. As seen above, we have specified the fields on our Post
model, which we want to be returned as JSON when we make a request for posts.
We are also explicitly casting the dates, created_at
and update_at
, to strings so that they would be returned as date strings. The dates are normally an instance of Carbon.
Now that we have created a resource class for our Post
model, we can start building the API’s action methods in our PostController
and return instances of the PostResource
where we want.
The usual actions performed on a post include the following:
In the last article, we already implemented a kind of ‘Read’ functionality when we defined the all
and single
methods. These methods allow users to browse through posts on the homepage.
In this section, we will define the methods that will resolve our API requests for creating, reading, updating and deleting posts.
The first thing we want to do is import the PostResource
class at the top of the PostController.php
file:
1// File: ./app/Http/Controllers/PostController.php 2 use App\Http\Resources\PostResource;
Because we created the
PostController
as a resource controller, we already have the resource action methods included for us in thePostController.php
file, we will be updating them with fitting snippets of code.
In the PostController
update the store()
action method with the code snippet below. It will allow us to validate and create a new post:
1// File: ./app/Http/Controllers/PostController.php 2 public function store(Request $request) 3 { 4 $this->validate($request, [ 5 'title' => 'required', 6 'body' => 'required', 7 'user_id' => 'required', 8 'image' => 'required|mimes:jpeg,png,jpg,gif,svg', 9 ]); 10 11 $post = new Post; 12 13 if ($request->hasFile('image')) { 14 $image = $request->file('image'); 15 $name = str_slug($request->title).'.'.$image->getClientOriginalExtension(); 16 $destinationPath = public_path('/uploads/posts'); 17 $imagePath = $destinationPath . "/" . $name; 18 $image->move($destinationPath, $name); 19 $post->image = $name; 20 } 21 22 $post->user_id = $request->user_id; 23 $post->title = $request->title; 24 $post->body = $request->body; 25 $post->save(); 26 27 return new PostResource($post); 28 }
Here’s a breakdown of what this method does:
PostResource
, which in turn returns a JSON formatted response.What we want here is to be able to read all the created posts or a single post. This is possible because the apiResource()
method defines the API routes using standard REST rules.
This means that a GET
request to this address, http://127.0.0.1:800/api/posts, should be resolved by the index()
action method. Let’s update the index
method with the following code:
1// File: ./app/Http/Controllers/PostController.php 2 public function index() 3 { 4 return PostResource::collection(Post::latest()->paginate(5)); 5 }
This method will allow us to return a JSON formatted collection of all of the stored posts. We also want to paginate the response as this will allow us to create a better view on the admin dashboard.
Following the RESTful conventions as we discussed above, a GET
request to this address, http://127.0.0.1:800/api/posts/id, should be resolved by the show()
action method. Let’s update the method with the fitting snippet:
1// File: ./app/Http/Controllers/PostController.php 2 public function show(Post $post) 3 { 4 return new PostResource($post); 5 }
Awesome, now this method will return a single instance of a post resource upon API query.
Next, let’s update the update()
method in the PostController
class. It will allow us to modify an existing post:
1// File: ./app/Http/Controllers/PostController.php 2 public function update(Request $request, Post $post) 3 { 4 $this->validate($request, [ 5 'title' => 'required', 6 'body' => 'required', 7 ]); 8 9 $post->update($request->only(['title', 'body'])); 10 11 return new PostResource($post); 12 }
This method receives a request and a post id
as parameters, then we use route model binding to resolve the id
into an instance of a Post
. First, we validate the $request
attributes, then we update the title and body fields of the resolved post.
Let’s update the destroy()
method in the PostController
class. This method will allow us to remove an existing post:
1// File: ./app/Http/Controllers/PostController.php 2 public function destroy(Post $post) 3 { 4 $post->delete(); 5 6 return response()->json(null, 204); 7 }
In this method, we resolve the Post
instance, then delete it and return a 204 response code.
Our methods are complete. We have a method to handle our CRUD operations, however, we haven’t built the frontend for the admin dashboard.
At the end of the second article, we defined the HomeController@index()
action method like this:
1public function index(Request $request) 2 { 3 if ($request->user()->hasRole('user')) { 4 return view('home'); 5 } 6 7 if ($request->user()->hasRole('admin')) { 8 return redirect('/admin/dashboard'); 9 } 10 }
This allowed us to redirect regular users to the view home
, and admin users to the URL /admin/dashboard
. At this point in this series, a visit to /admin/dashboard
will fail because we have neither defined it as a route with a handler Controller nor built a view for it.
Let’s create the AdminController
with this command:
$ php artisan make:controller AdminController
We will add the /admin/
route to our routes/web.php
file:
Route::get('/admin/{any}', 'AdminController@index')->where('any', '.*');
Note that we wrote
/admin/{any}
here because we intend to serve every page of the admin dashboard using the Vue router. When we start building the admin dashboard in the next article, we will let Vue handle all the routes of the/admin
pages.
Let’s update the AdminController.php
file to use the auth middleware and include an index()
action method:
1// File: ./app/Http/Controllers/AdminController.php 2 <?php 3 4 namespace App\Http\Controllers; 5 6 class AdminController extends Controller 7 { 8 public function __construct() 9 { 10 $this->middleware('auth'); 11 } 12 13 public function index() 14 { 15 if (request()->user()->hasRole('admin')) { 16 return view('admin.dashboard'); 17 } 18 19 if (request()->user()->hasRole('user')) { 20 return redirect('/home'); 21 } 22 } 23 }
In the index()
action method, we included a snippet that will ensure that only admin users can visit the admin dashboard and perform CRUD operations on posts.
We will not start building the admin dashboard in this article but will test that our API works properly. We will use Postman to make requests to the application.
Let’s test that our API works as expected. We will, first of all, serve the application using this command:
$ php artisan serve
We can visit http://localhost:8000 to see our application and there should be exactly two posts available; these are the posts we seeded into the database during the migration:
When testing with Postman always set the
Content-Type
header toapplication/json
.
Now let’s create a new post over the API interface using Postman. Send a POST
request as seen below:
Now let’s update this post we just created. In Postman, we will pass only the title
and body
fields to a PUT
request.
To make it easy, you can just copy the payload below and use the raw request data type for the Body:
1{ 2 "title": "We made an edit to the Post on APIs", 3 "body": "To a developer, 'What's an API?' might be a straightforward - if not exactly simple - question. But to anyone who doesn't have experience with code. APIs can come across as confusing or downright intimidating." 4 }
We could have used the PATCH method to make this update, the PUT and PATCH HTTP verbs both work well for editing an already existing item.
Finally, let’s delete the post using Postman:
We are sure the post is deleted because the response status is 204 No Content
as we specified in the PostController
.
In this chapter, we learned about Laravel’s API resources and we created a resource class for the Post model. We also used the apiResources()
method to generate API only routes for our application. We wrote the methods to handle the API operations and tested them using Postman.
In the next part, we will build the admin dashboard and develop the logic that will enable the admin user to manage posts over the API.
The source code for this project is available here on Github.