Simple and Easy REST API Implementation With Laravel

Saturday, March 19, 2022

Posted by Zydhan Linnar Putra

What is API?

API is a method where two or more software can communicate each other.

What is REST API

REST (Representational State Transfer) API is an architecture that allow server and client to transfer the representation of the state of the resource to the requester or endpoint. The data can be transferred using JSON, XML, or another format but the most popular option is JSON (JavaScript Object Notation). You can watch this video if you feel don't familiar with JSON format.

Prerequisites

Project Initialization

First, let initialize our Laravel project. This should automatically install the latest version of Laravel.

composer create-project laravel/laravel rest-api-implementation-laravel
cd rest-api-implementation-laravel

Then ensure the server can be up and running:

php artisan serve

Then open http://127.0.0.1:8000, you should see this sweet Laravel welcome screen:

Laravel default welcome screen

Setting Up Database

I want to make this tutorial to be as simple as possible, so we're going to use SQLite3 for our database connection.

First, install sqlite and create sqlite file:

# Change 8.1 according to your php version
sudo apt install php8.1-sqlite3 sqlite3

touch database.sqlite

Open your .env file and change DB_CONNECTION to sqlite, then delete DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD:

DB_CONNECTION=sqlite

Creating Migrations and Model

We want to create model and migration for Developer table. Fortunately, Laravel provides easy way to make model and migrations with one single command:

php artisan make:model Developer --migration

Then, you will have two new files app/Models/Developer.php for the model and database/migrations/2022_03_18_090902_create_developers_table.php for the migrations. Note that the timestamp of migration file depends on when you execute the command.

Created model and migrations

Migrate table

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('developers', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

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

Short explanation:

  • up() method will be called when we run the migrations
  • down() method will be called when we rollback the migrations
  • Schema::dropIfExists('developers') will drop developers table from database if exists
  • Schema::create('developers') will create developers table
  • $table->id() will create incremental primary key with name id
  • $table->timestamps() will add created_at and updated_at columns to the table

Supposed we want to add name and fav_lang columns with string type, we can modify the migration to be like this:

Schema::create('developers', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('fav_lang');
    $table->timestamps();
});

Then, run the migration:

php artisan migrate

Creating Controller

To make simple controller for API, we can use default artisan command with --model and --api arguments:

php artisan make:controller DeveloperController --api --model=Developer

Here is our controller:

<?php

namespace App\Http\Controllers;

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

class DeveloperController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * 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\Developer  $developer
     * @return \Illuminate\Http\Response
     */
    public function show(Developer $developer)
    {
        //
    }

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

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

Look, our controller methods have $developer argument. Laravel will find record in database automatically for Developer table with Route model binding.

Setting Up Routes

Now, open routes/api.php and add DeveloperController to the route:

Route::apiResource('developers', DeveloperController::class);

Now check our route list:

php artisan route:list

Route list for Developer resource

Short explanations:

  • GET method for api/developers will be handled by index() method in DeveloperController. This route is for display a listing of the resource.
  • GET method for api/developers/{developers} will be handled by show() method in DeveloperController. This route is for display specific resource.
  • POST method for api/developers will be handled by store() method in DeveloperController. This route is for creating new resource.
  • PUT/PATCH method for api/developers/{developers} will be handled by update() method in DeveloperController. This route is for updating specific resource.
  • DELETE method for api/developers/{developers} will be handled by destroy() method in DeveloperController. This route is for deleting specific resource.

Handling Request

Creating Resource

When creating new resource, we need to take input name and fav_lang from user and put it to the database.

public function store(Request $request)
{
    $developer = new Developer();
    $developer->name = $request->input('name');
    $developer->fav_lang = $request->input('fav_lang');

    $developer->save();
    return response()->json([ 'message' => 'Resource created' ]);
}

Test Create Resource API

We're going to make POST request to http://localhost:8000/api/developers using Postman. First, we need to set Content-Type and Accept header to application/json to make sure we always receive JSON response from Laravel.

Content-Type and Accept header

Now, we want to insert developer with name Zydhan Linnar Putra and his favourite languange which is C++:

Resource successfully created

Nice! We received response that we want.

Listing Resources

We only need to return all records in database using Developer::all():

public function index()
{
    return Developer::all();
}

Test Resources Listing

To test this functionality, we should make GET request to http://localhost:8000/api/developers:

Resource listing Postman

Showing Specific Resource

We only need to return $developer from method parameter:

public function show(Developer $developer)
{
    return $developer;
}

Test Display Specific Resource

We should make GET request to http://localhost:8000/api/developers/{resource_id}. According to resources listing the resource id is 1, so we need to test http://localhost:8000/api/developers/1:

Specific resource Postman

Updating resources

Like resource creation, we also need to take input from user, and update it to database. But for updating, we don't need to create new Developer instance, we only need to update instance that given in method param:

public function update(Request $request, Developer $developer)
{
    $developer->name = $request->input('name');
    $developer->fav_lang = $request->input('fav_lang');

    $developer->save();
    return response()->json([ 'message' => 'Resource updated' ]);
}

Test Update Specific Resource

Make a PUT request to http://localhost:8000/api/developers/1 with body structure resource creation, but in this case we want to change his name to Zydhan and make PHP as his favourite language:

Resource update Postman

Then, confirm if the resource is updated:

resource successfully updated Postman

If you look at it, Laravel also automatically update updated_at column.

Delete resource

Eloquent provide delete() method to delete resource from table:

public function destroy(Developer $developer)
{
    $developer->delete();

    return response()->json([ 'message' => 'Resource deleted' ]);
}

Test resource deletion

Method for this request is DELETE and we want to delete developer with name Zydhan which has ID of 1. So we make DELETE request to http://localhost:8000/api/developers/1:

Resource deletion Postman

Now if we access resource listing, we should receive empty array:

Empty array Postman

And if we try to access with resource ID, we got 404 not found:

Not found Postman

Verdict

Laravel bring us ease of API development with eloquent, API controller, migration, etc. There are many room of improvements here, right now we don't have any authentication method so that random user could access our API and do whatever they want. Let me know if you want i make the tutorial to secure your API!

Bonus

API Resources

Typically, you don't want to return all column for listing resources, to achieve it, you can use API Resource to transform eloquent model before returning it.

First, create resource file:

php artisan make:resource DeveloperResource

We only want to return developer ID and its name, so we only put those columns to resource file:

public function toArray($request)
{
    return [
        'id' => $this->id,
        'name' => $this->name
    ];
}

Then, in DeveloperController@index, we should call resource file when returning it.

public function index()
{
    return DeveloperResource::collection(Developer::all());
}

DeveloperResource testing

Now, it only return id and name. If we look closer, it also add data, that will be useful if you use pagination. You can read more about API resources here.

Input Validation

What if we don't fill all the input when creating resource? Let's try it:

Test unfilled input

Wow, we got Internal server error. We shouldn't return those error because the problem is from the user. Instead, we should validate data before doing any business logic. First, create request file:

php artisan make:request StoreDeveloperRequest

Now we have new file app/Http/Requests/StoreDeveloperRequest.php:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            //
        ];
    }
}

Short explanation:

  • authorize() method is for checking whether it is authorized to execute that request. For now, set it to true to authorize all request.
  • rules() method is for validating input.

Now, we want to make name and fav_lang to be required. Let add it to the rules array:

public function rules()
{
    return [
        'name' => 'required',
        'fav_lang' => 'required'
    ];
}

Then, in the controller, substitute Request with StoreDeveloperRequest:

public function store(StoreDeveloperRequest $request)
{
    $developer = new Developer();
    $developer->name = $request->input('name');
    $developer->fav_lang = $request->input('fav_lang');

    $developer->save();
    return response()->json([ 'message' => 'Resource created' ]);
}

Test it again:

Test storing unprocessable content

Now we got proper 422 - Unprocessable Content error and message that we must fill the name and fav_lang column. You can read more about another validation rules beside required here.

Reference

Delete comment

Are you sure want to delete this comment?

Comments

Loading...

I'm sorry, but i can't load all comments for now 😢

Add new comment

Log in to add comment.