Laravel RESTful API | How to make RESTful API using Laravel 8

Hello Software Architect !

If you have landed in this page by reading the title then I consider that you have basic knowledge of Laravel and one of database system like MYSQL or Sqlite. In if you don't know at all about these then it will spin your head little bit but don't worry. I will explain everything from start to end.

I Personally believe that creating a RESTful API using Laravel is so easy , efficient and reliable. We just need to understand the basic of API request and response that should be returned in JSON with some status code. That much easy its.

So without bothering you lets jump into our main goal of creating a demo CRUD RESTful API using Laravel 8.x. Hope you have all the necessary set-up in your local machine to run Laravel 8.x or higher properly. Or you can check one of my article to install Laravel Properly.

Table Of Contents

1. What is RESTful API ?

2. Required Software and Tools.

3. Create A Fresh New Laravel Project.

4. Create Model, Migrations, Resourceful Controller, Factory and Seeder.

5. Create Database Schema and Run the migration.

6. Seed Dummy Data for Testing.

7. Add CRUD Logic and Test Using Postman. (If already have good knowledge of Laravel you can directly jump here)

1. What is RESTful API?

REST stands for representational state transfer and was created by computer scientist Roy Fielding. And the term API means application programming interface (API or web API) that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services. And hence also know as REST API or RESTful API.

Due to the fact that variety on smart devices and also multiple front-end frameworks and also to make application data shareable between cross platform with single interface RESTful API come in handy.

Laravel is being the most popular web framework by this time. As Laravel is very powerful, provides lots of features, it is reliable, has great community support, uses MVC design pattern and fully customizable.

So lets see how we can make RESTful API using Laravel. I am using Laravel 8.x in this example demo tutorial.

2. Required Software and Tools.

Let's look at these technologies as we build our API:

3. Create A Fresh New Laravel Project

As I said earlier, we will see step by step from scratch. So lets go to your favorite/working directory and create a fresh new Laravel project with following command in your terminal.

Using composer

composer create-project laravel/laravel demo-rest-api 

Using Laravel Global Installer

laravel new demo-rest-api 

Now time to serve our project in local server. For that run your Xampp server(apache and mySQL service) first. Then navigate to our project directory $ cd demo-rest-api and use artisan serve.

 php artisan serve 

You will see following output in your terminal:

Starting Laravel development server: http://127.0.0.1:8000
[Wed Feb 17 23:31:46 2021] PHP 7.4.6 Development Server (http://127.0.0.1:8000) started 

If you visit http://localhost:8000 or 127.168.0.1:8000 you will see Laravel's welcome page.

4. Create Model, Migrations, Resourceful Controller, Factory and Seeder.

For now close the local server or you can open new terminal in the same directory. So that we can create everything (model, migration and resourceful controller,factory and seeder) using following command:

php artisan make:model Article -a 

Output will look like this:

Model created successfully.
Factory created successfully.
Created Migration: 2021_02_17_175802_create_articles_table
Seeder created successfully.
Controller created successfully. 

-a stands for all you can also use --all flag.

Or you can create them separately one by one. If you don't want to create factory and seeder use this command

php artisan make:model Article -mcr 

Here -mcr flag stands for migration, controller, resourceful. you can remove cr for only model and migration.

The above first artisan command (i.e. with -a flag) will create 5 different files in the following directories:

  • app\Models\Article.php : The model.
  • database\migrations\2021_02_17_175802_create_articles_table.php : The migration.
  • app\Http\Controllers\ArticleController.php : The controller.
  • app\Http\Controllers\ArticleController.php : The Factory.
  • database\seeders\ArticleSeeder.php : The seeder.

We will see each of the file in up-coming steps one by one and do necessary modification.

So lets dive deeper step by step.

5. Create Database Schema and Run the migration.

Open the project with code editor of your choice. I am using Vs-code :slightly_smiling_face:

In this example I will show how we can Create , Read, Update and Delete (CRUD) an Article in our database. So I will keep the database schema simple and pretty basic with few columns.

Lets say we have an Article table with columns like id, title, description, image, status , created_at, and updated_at.

So lets open the newly created database\migrations\2021_02_17_175802_create_articles_table.php file in the code editor and add following code inside up()

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description');
            $table->string('image')->nullable();
            $table->enum('status', ['Published', 'Draft'])->default('Published');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
} 

Also open app\Models\Article.php and add fillable variables according to our schema

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'description', 'image', 'status'];
} 

Now time to create New MySQL Database for that open new tab on your favorite web browser and go to localhost/phpmyadmin and click new and give you database a name (in my case its demo-rest-api) and lick on create

Lets add the database detail on our application environment variables. So open .env file and fill the details. Below is my setup for reference :

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE= demo-rest-api
DB_USERNAME= root
DB_PASSWORD= 

Then lets save the file. In Laravel 8.x the server will restart automatically. If not in your terminal hit Ctrl + C and again start the server.

Time to migrate our database now.

php artisan migrate 

Your output will look like this

Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (629.29ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (511.12ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (488.86ms)
Migrating: 2021_02_17_175802_create_articles_table
Migrated:  2021_02_17_175802_create_articles_table (218.20ms) 

6. Seed Dummy Data for Testing.

To add some dummy test data in our database lets open our ArticleFactory and in the run method add following code.

database\factories\ArticleFactory.php

<?php

namespace Database\Factories;

use App\Models\Article;
use Illuminate\Database\Eloquent\Factories\Factory;

class ArticleFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = Article::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'title' => $this->faker->sentence(6),
            'description' => $this->faker->paragraph(4),
            'image' => $this->faker->imageUrl(640, 480),
            'status' => $this->faker->randomElement(['Published', 'Draft'])
        ];
    }
} 

Open DatabaseSeeder and add Article::factory(10)->create(); inside run() method

database\seeders\DatabaseSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Article;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        // \App\Models\User::factory(10)->create();
        Article::factory(10)->create();
    }
} 

Now in your terminal run database seeder by using

 php artisan db:seed 

After you get the output message Database seeding completed successfully. congratulations you have successfully all the basic steps of our project. To check all the seeded data use tinker as follows:

php artisan tinker 
$articles = Article::all(); 

you will see somthing like the image below:

Get all articles using tinker

Now its time to do some thing amazing. :wink:

7. Add CRUD Logic and Test Using Postman.

Finally here we arrived at our main and most important section of this article. In previous steps we have done many thing but all of them are just basic stuffs. We have still many things to understand and take special care of them. As always I will go step by stem through them. We will divide our works on following steps:

A. Registering route, And Writing our index method to get all the articles.

B. Create Article Request and Write Store Logic.

C. Add show Article Logic.

D. Update the Article.

E. Destroy Article.

A. Registering route, And Writing our index method to get all the articles.

In our routes directory we have four files and one of them is api.php . In Laravel all the API routes prefixed with /api So all the api routes should belong here and as always we can customize it as well but that for another time.

Lets open routes\api.php file and register our index route.

routes\api.php

<?php

use App\Http\Controllers\ArticleController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
}); // default example route


//our route to get all posts
Route::get('/articles', [ArticleController::class, 'index']); 

Open ArticleController and add following code inside index method.

app\Http\Controllers\ArticleController.php

<?php

namespace App\Http\Controllers;

use App\Models\Article;
use Illuminate\Http\Request;

class ArticleController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
         $articles = Article::all();
        return response()->json(['articles' => $articles], 200);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Article  $article
     * @return \Illuminate\Http\Response
     */
    public function show(Article $article)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Article  $article
     * @return \Illuminate\Http\Response
     */
    public function edit(Article $article)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Article  $article
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Article $article)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Article  $article
     * @return \Illuminate\Http\Response
     */
    public function destroy(Article $article)
    {
        //
    }
} 

now in your browser go to localhost/api/articles and you will see all the post in json format as below.

See all articles in json chrome screen shot

If you don't have json formatter extension you will see raw data. You can add json formatter extension on any browser.

But this is not the way we test. For that lest open Postman and make are get request in our route. I hope you have basic idea of post man if not please google it and come back.

Oky if you make get request from postman you will get the result as below

Ger all posts using postman

In same way we need to register routes for show , store, edit, update and delete. So lets put a single resource route instead of all 6 routes. Replace the previous route with following route.

<?php

use App\Http\Controllers\ArticleController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
}); // default example route


//our route to get all posts
// Route::get('/articles', [ArticleController::class, 'index']); // remove this

//Resource route for Article controller
Route::resource('/articles', ArticleController::class); 

**B. Create a Request and Write Store Logic **

To take valid input from user Its always good approach to make Request/Validator. So lets make ArticalRequest for both store and update method to get valid input from clients.

php artisan make:request ArticalRequest 

Lets open the file and put validation rules.

app\Http\Requests\ArticleRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ArticleRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        // For the store method
        if (request()->isMethod('post')) {
            return [
                'title' => 'required|string|max:258',
                'description' => 'required|string',
                'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
                'status' => 'in:Published,Draft',
            ];
        }

        // For the update method (i.e. put or patch method)
        else {
            return [
                'title' => 'required|string|max:258',
                'description' => 'required|string',
                'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
                'status' => 'in:Published,Draft'
            ];
        }
    }

    /**
     * Custom message for validation
     *
     * @return array
     */
    public function messages()
    {
        if (request()->isMethod('post')) {
            return [
                'title.required' => 'Title field is required!',
                'description.required' => 'Descritpion field is required!',
                'image.mimes' => 'Acceptable file types jpeg,png,jpg,gif,svg!',
                'status.in' => 'Save as Published or Draft'
            ];
        } else {
            return [
                'title.required' => 'Title field is required!',
                'description.required' => 'Descritpion field is required!',
                'image.mimes' => 'Acceptable file types jpeg,png,jpg,gif,svg!',
                'status.in' => 'Save as Published or Draft'
            ];
        }
    }
} 

Now lets open our controller and write logic to store our article.

app\Http\Controllers\ArticleController.php

// use these on top
use App\Http\Requests\ArticleRequest;
use Illuminate\Support\Facades\Storage;


/**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(ArticleRequest $request)
    {
        try {
            if ($request->image) {
                //Get image name
                $imageName =  microtime() . "-" . $request->image->getClientOriginalName();
                // Save Image in Storage folder
                Storage::disk('public')->put($imageName, file_get_contents($request->image));
                // Create Article
                Article::create([
                    'title' => $request->title,
                    'image' => asset(Storage::url($imageName)),
                    'description' => $request->description,
                    'status' => $request->status,
                ]);
            } else {
                // In case of no image
                Article::create($request->all());
            }
            // Return Success Response
            return response()->json([
                'message' => "Article creaded successfully."
            ], 201);
        } catch (\Exception $e) {
            // Return Error Response
            return response()->json([
                'message' => "Something seems not good today!"
            ], 500);
        }
    } 

Its time to create a new article using using postman. I am really excited for this one. Lets jump to postman and lest send POST request with following parameters.

title : new test title

``description : new test description`

status : Draft

image : (type:file) choose a file >> upload any image of type jpg or png as in our validator mime type.

as below:

Send post request using postman

To upload file, in the key section select File in default its Text .

If you get Error like Invalid input or server error. Go to Headers section and remove/uncheck Content-type : apllication/json and it should work.

If you have done correctly, you will get response as below

{
    "message": "Article Creatde Succefully!."
} 

C. Add show Article Logic

In the show method remove the model binding with $id and add following code.

/**
     * Display the specified resource.
     *
     * @param  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id) // Remove the model binding
    {
        $article = Article::find($id);
        if ($article != null) {
            return response()->json(['article' => $article]);
        } else {
            return response()->json(["message" => "Article Not Found!"], 404);
        }
    } 

Send GET request to the to /articles/11

and you will get

{
    "article": {
        "id": 11,
        "title": "new title",
        "description": "test description",
        "image": "http://127.0.0.1:8000/storage/0.31285700 1613682659-avatar.png",
        "status": "Draft",
        "created_at": "2021-02-18T21:10:59.000000Z",
        "updated_at": "2021-02-18T21:10:59.000000Z"
    }
} 

By clicking the image link you can see the image and if not run php artisan storage:link

D. Update the Article

To update add update article logic inside update methode.

/**
     * Update the specified resource in storage.
     *
     * @param  App\Http\Requests\ArticleRequest  $request
     * @param  int $id;
     * @return \Illuminate\Http\Response;
     */
    public function update(ArticleRequest $request, $id)
    {
        try {
            $article = Article::find($id);
            if (!$article) {
                return response()->json(["message" => "Article Not Found!"], 404);
            }
            if ($request->image) {
                //Get image name
                $imageName =  microtime() . "-" . $request->image->getClientOriginalName();
                // Save Image in Storage folder
                Storage::disk('public')->put($imageName, file_get_contents($request->image));
                // Delete old image from storage if exist any
                if ($article->image) {
                    $oldimage = explode('storage/', $article->image);
                    Storage::disk('public')->delete($oldimage['1']);
                }

                // Update Article
                $article->update([
                    'title' => $request->title,
                    'image' => asset(Storage::url($imageName)),
                    'description' => $request->description,
                    'status' => $request->status,
                ]);
            } else {
                $article->update([$request->all()]);
            }
            // Return Success Response
            return response()->json([
                'message' => "Article Updated Succesfully."
            ], 200);
        } catch (\Exception $e) {
            // Return Error Response
            return response()->json([
                'message' => "Something Seems Not Good Today!"
            ], 500);
        }
    } 

To send PUT/PATCH request from Postman in Laravel its little tricky. See the screenshot and figureout.

Sending PUT/PATCH request using postman

you need to select POST request and in the body section provide _methode : PUT.

E. Destroy Article

Finally we have moved to destroy method of our controller. Add the following code on destroy method.

 /**
     * Remove the specified resource from storage.
     *
     * @param int $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $article = Article::find($id);
        if ($article) {
            $article->delete();
            return response()->json(['message' => 'Article Deleted Succefully!'], 200);
        } else {
            return response()->json(['message' => 'Article Not Found'], 404);
        }
    } 

Now you can delete specific article by DELETE request from postman and response will look like this.

{
    "message": "Article Deleted Succefully!"
} 

Congratulations you have successfully made RESTful API for CRUD using Laravel.

you can clean code for deleting old image by adding another method like deleteImage() and call it when needed as follow.

protected function deleteImage($article)
    {
        if ($article->image) {
            $oldimage = explode('storage/', $article->image);
            Storage::disk('public')->delete($oldimage['1']);
        }
    } 

Calling the function from anywhere inside the controller.

Example : in destroy method

public function destroy($id)
    {
        $article = Article::find($id);
        if ($article) {
            $this->deleteImage($article); // this will delete the old image from storage.
            $article->delete();
            return response()->json(['message' => 'Article Deleted Succefully!'], 200);
        } else {
            return response()->json(['message' => 'Article Not Found'], 404);
        } 

BONUS : Some HTTP Status Codes

Sending data using JSON response i.e. response()->json(), lets us explicitly return JSON data and send an HTTP code that the client can parse it easily. Here I have listed some commonly use status codes below as bunus.

  • 200 : OK.
  • 201 : Created. Object created
  • 204 : No Content. Action was executed successfully, no thing to return.
  • 206 : Partial Content. returning a paginated list of resources.
  • 400 : Bad Request.
  • 401 : Unauthorized.
  • 403 : Forbidden.
  • 404 : Not Found.
  • 422 : Un processable Entity . when validation fails.
  • 500 : Internal Server Error.
  • 503 : Service Unavailable.

Before closing

In the article I have explained every step to create a demo RESTful API. Basically the whole CRUD functionality. Hope this article adds value, and in future article I will try to extend this project for other features. So please stay connected with us. If you have any questions/suggestions please drop in the comment section. Thanks for reading. :beer:

Comments :

  • Mandip Shahee 5 months ago

    The content was very helpful... thank you !!