Rest API Development With Lumen(5.4): Part 2

In the first part of Rest API Development With Lumen, we installed Lumen and set up local development server using homestead. In this part, we will start coding.

You can see the finished version of this series in Github.

Application Key and .env file
The first thing we need to do is set random string for application key. Typically, this string should be 32 characters long. Create a route name appKey for generating random string in routes/web.php file.

//routes/web.php

$app->get('appKey', function () {
    return str_random('32');
});

Now, if we visit http://restapi.app/appKey we will see the 32 characters random string. We need to put that string in the .env file. All the configuration options for the Lumen framework are stored in that file and this file should not be version controlled.

APP_KEY=Gfl7u2OcTQjPIPDMoi4ckoS4jTpGXqlE

We also need to create a .env.example file which is an exact copy of the .env file. This file should be version control that way another developer can create their own .env file.

Creating First Resource
The first resource we’re going to create is users resource.

//routes/web.php

$app->get('users', '[email protected]');
$app->post('users', '[email protected]');
$app->get('users/{id}', '[email protected]');
$app->put('users/{id}', '[email protected]');
$app->delete('users/{id}', '[email protected]');

We need to create a Controller for that resource. Let’s create UserController.

<?php //app/Http/Controllers/UserController.php


namespace App\Http\Controllers;


use Illuminate\Http\Request;


class UserController extends Controller
{
    public function index(Request $request)
    {
    }

    public function store(Request $request)
    {
    }

    public function update(Request $request, $id)
    {
    }

    public function show($id)
    {
    }

    public function destroy($id)
    {
    }
}

As of now, our Controller has five empty methods. The index method is for getting all records, store method is for storing data, update method is for updating a specific record, show method is for showing a specific record and the destroy method is for deleting a specific record

We’re going to use Postman for testing our API. You can download and install Postman from here https://www.getpostman.com/

Screen Shot 2017-03-01 at 10.37.14 AM

Creating Database
We need to create a database for our API. Let’s create a databse name restpai. Mac users can use Sequel Pro for managing MySql database. Below is the screenshot for Sequel Pro connection.
Screen Shot 2017-03-01 at 4.20.02 PM

Or using command line

mysql> CREATE DATABASE restapi;

Also you will need to update .env and .env.example file for the new database.

DB_DATABASE=restapi

Creating Migration
Migrations are like version control for your database, allowing your team to easily modify and share the application’s database schema. To create a migration file, use the make:migration Artisan command.

php artisan make:migration create_users_table --create=users

The new migration will be placed in database/migrations directory. For more info about migration visit Lumen/Laravel documentation.

Add the following codes.

<?php

    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('uid', 36)->unique();
            $table->string('firstName', '100')->nullable();
            $table->string('lastName', '100')->nullable();
            $table->string('middleName', '50')->nullable();
            $table->string('username', '50')->nullable();
            $table->string('email')->unique();
            $table->string('password')->nullable();
            $table->string('address')->nullable();
            $table->string('zipCode')->nullable();
            $table->string('phone')->nullable();
            $table->string('mobile')->nullable();
            $table->string('city', '100')->nullable();
            $table->string('state', '100')->nullable();
            $table->string('country', '100')->nullable();
            $table->string('type')->nullable();
            $table->tinyInteger('isActive');
            $table->timestamps();
            $table->softDeletes();
        });
    }

One note about the uid field. Instead of using mysql generated auto-incremented id as primary identifier we are going to use uid. You can read from here https://philsturgeon.uk/http/2015/09/03/auto-incrementing-to-destruction/ why auto-incrementing id is bad. For generating uid we are going to use this php package https://github.com/ramsey/uuid

Running Migrations
To run all migrations, use the migrate Artisan command.

php artisan migrate

Creating Model
Lumen doesn’t come with Models directory. Create the Models directory and put User model inside the Models directory.

<?php //app/Models/User.php

namespace App\Models;

use Illuminate\Auth\Authenticatable;
use Laravel\Lumen\Auth\Authorizable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model implements AuthenticatableContract, AuthorizableContract
{
    use Authenticatable, Authorizable, SoftDeletes;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'uid',
        'firstName',
        'lastName',
        'middleName',
        'email',
        'password',
        'address',
        'zipCode',
        'username',
        'city',
        'state',
        'country',
        'phone',
        'mobile',
        'type',
        'isActive'
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];
}

Creating Repositories
Instead of using Model directly in our app we are going to use Repository. There are several benefits using Repository. With Repository, we can swap the implementation of Model anytime. For example: instead of using Eloquent Model if we want we can use Symfony Doctrine ORM or an API.

Create Repositories directory inside app directory. For each repository, we’re going to create an Interface and Implementation of that Interface.
First, we’re going to create a BaseRepository interface. All others interface going to extend the BaseRepository interface.

<?php //app/Repositories/Contracts/BaseRepository.php

namespace App\Repositories\Contracts;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;

interface BaseRepository
{
    /**
     * find a resource by id
     *
     * @param $id
     * @return Model|null
     */
    public function findOne($id);

    /**
     * find a resource by criteria
     *
     * @param array $criteria
     * @return Model|null
     */
    public function findOneBy(array $criteria);

    /**
     * Search All resources
     *
     * @param array $searchCriteria
     * @return Collection
     */
    public function findBy(array $searchCriteria = []);

    /**
     * Search All resources by any values of a key
     *
     * @param string $key
     * @param array $values
     * @return Collection
     */
    public function findIn($key, array $values);

    /**
     * save a resource
     *
     * @param array $data
     * @return Model
     */
    public function save(array $data);

    /**
     * update a resource
     *
     * @param Model $model
     * @param array $data
     * @return Model
     */
    public function update(Model $model, array $data);

    /**
     * delete a resource
     *
     * @param Model $model
     * @return mixed
     */
    public function delete(Model $model);

    /**
     * updated records by specific key and values
     *
     * @param string $key
     * @param array $values
     * @param array $data
     * @return Collection
     */
    public function updateIn($key, array $values, array $data);
}

Here, we’ve created a BaseRepository interface that has all the necessary methods to interact with the database. Create an abstract class name AbstractEloquentRepository that implements BaseRepository interface.

<?php //app/Repositories/AbstractEloquentRepository.php

namespace App\Repositories;

use App\Repositories\Contracts\BaseRepository;
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;

abstract class AbstractEloquentRepository implements BaseRepository
{
    /**
     * Name of the Model with absolute namespace
     *
     * @var string
     */
    protected $modelName;

    /**
     * Instance that extends Illuminate\Database\Eloquent\Model
     *
     * @var Model
     */
    protected $model;


    public function __construct()
    {
        $this->setModel();
    }

    /**
     * Instantiate Model
     *
     * @throws \Exception
     */
    public function setModel()
    {
        //check if the class exists
        if (class_exists($this->modelName)) {
            $this->model = new $this->modelName;

            //check object is a instanceof Illuminate\Database\Eloquent\Model
            if (!$this->model instanceof Model) {
                throw new \Exception("{$this->modelName} must be an instance of Illuminate\Database\Eloquent\Model");
            }

        } else {
            throw new \Exception('No model name defined');
        }
    }

    /**
     * Get Model instance
     *
     * @return Model
     */
    public function getModel()
    {
        return $this->model;
    }

    /**
     * @inheritdoc
     */
    public function findOne($id)
    {
        return $this->findOneBy(['uid' => $id]);
    }

    /**
     * @inheritdoc
     */
    public function findOneBy(array $criteria)
    {
        return $this->model->where($criteria)->first();
    }

    /**
     * @inheritdoc
     */
    public function findBy(array $searchCriteria = [])
    {
        $limit = !empty($searchCriteria['per_page']) ? (int)$searchCriteria['per_page'] : 15; // it's needed for pagination

        $queryBuilder = $this->model->where(function ($query) use ($searchCriteria) {

            $this->applySearchCriteriaInQueryBuilder($query, $searchCriteria);
        }
        );

        return $queryBuilder->paginate($limit);
    }


    /**
     * Apply condition on query builder based on search criteria
     *
     * @param Object $queryBuilder
     * @param array $searchCriteria
     * @return mixed
     */
    protected function applySearchCriteriaInQueryBuilder($queryBuilder, array $searchCriteria = [])
    {

        foreach ($searchCriteria as $key => $value) {

            //skip pagination related query params
            if (in_array($key, ['page', 'per_page'])) {
                continue;
            }

            //we can pass multiple params for a filter with commas
            $allValues = explode(',', $value);

            if (count($allValues) > 1) {
                $queryBuilder->whereIn($key, $allValues);
            } else {
                $operator = '=';
                $queryBuilder->where($key, $operator, $value);
            }
        }

        return $queryBuilder;
    }

    /**
     * @inheritdoc
     */
    public function save(array $data)
    {
        // generate uid
        $data['uid'] = Uuid::uuid4();

        return $this->model->create($data);
    }

    /**
     * @inheritdoc
     */
    public function update(Model $model, array $data)
    {
        $fillAbleProperties = $this->model->getFillable();

        foreach ($data as $key => $value) {

            // update only fillAble properties
            if (in_array($key, $fillAbleProperties)) {
                $model->$key = $value;
            }
        }

        // update the model
        $model->save();

        // get updated model from database
        $model = $this->findOne($model->uid);

        return $model;
    }

    /**
     * @inheritdoc
     */
    public function findIn($key, array $values)
    {
        return $this->model->whereIn($key, $values)->get();
    }

    /**
     * @inheritdoc
     */
    public function delete(Model $model)
    {
        return $model->delete();
    }
}
   

Now, create UserRepository and the implementation of the repository name EloquentUserRepository.

<?php //app/Repositories/Contracts/UserRepository.php

namespace App\Repositories\Contracts;

interface UserRepository extends BaseRepository
{
}
<?php //app/Repositories/EloquentUserRepository.php

namespace App\Repositories;

use App\Repositories\Contracts\UserRepository;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

class EloquentUserRepository extends AbstractEloquentRepository implements UserRepository
{
    /**
     * Model name
     *
     * @var string
     */
    protected $modelName = User::class;


    /*
     * @inheritdoc
     */
    public function save(array $data)
    {
        // update password
        if (isset($data['password'])) {
            $data['password'] = Hash::make($data['password']);
        }

        $user = parent::save($data);

        return $user;
    }
}

Service Provider and Service Container
Lumen service container allows us to manage class dependencies and perform dependency injection. So far, we have created two interfaces and implementation of the interface. Let’s bind those interfaces to the implementation that way Lumen will know which implementation to use for given interface.

Create RepositoriesServiceProvider

<?php //app/Providers/RepositoriesServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repositories\Contracts\UserRepository;
use App\Repositories\EloquentUserRepository;

class RepositoriesServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(UserRepository::class, EloquentUserRepository::class);
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [
            UserRepository::class
        ];
    }
}

Registering Service Providers
Register the service provider in bootstrap/app.php file.

$app->register(App\Providers\RepositoriesServiceProvider::class);

For details about Lumen service container and service provider visit Lumen documentation

The last thing we need to do is enable facade and eloquent.

 $app->withFacades();

 $app->withEloquent();

Update UserController
Finally, we need to update the UserController to use Repository.

<?php //app/Http/Controllers/UserController.php

namespace App\Http\Controllers;

use App\Models\User;
use App\Repositories\Contracts\UserRepository;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Instance of UserRepository
     *
     * @var UserRepository
     */
    private $userRepository;

    /**
     * Assign the validatorName that will be used for validation
     *
     * @var string
     */
    protected $validatorName = 'User';

    /**
     * Constructor
     *
     * @param UserRepository $userRepository
     */
    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    /**
     * Display a listing of the resource.
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function index(Request $request)
    {
        $users = $this->userRepository->findBy($request->all());

        return response()->json(['data' => $users], 200);
    }

    /**
     * Display the specified resource.
     *
     * @param $id
     * @return \Illuminate\Http\JsonResponse|string
     */
    public function show($id)
    {
        $user = $this->userRepository->findOne($id);

        if (!$user instanceof User) {
            return response()->json(['message' => "The user with id {$id} doesn't exist"], 404);
        }

        return response()->json(['data' => $user], 200);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse|string
     */
    public function store(Request $request)
    {
        $user = $this->userRepository->save($request->all());

        if (!$user instanceof User) {
            return response()->json(['message' => "Error occurred on creating user"], 500);
        }

        return response()->json(['data' => $user], 201);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param Request $request
     * @param $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function update(Request $request, $id)
    {
        $user = $this->userRepository->findOne($id);

        if (!$user instanceof User) {
            return response()->json(['message' => "The user with id {$id} doesn't exist"], 404);
        }

        $inputs = $request->all();

        $user = $this->userRepository->update($user, $inputs);

        return response()->json(['data' => $user], 200);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param $id
     * @return \Illuminate\Http\JsonResponse|string
     */
    public function destroy($id)
    {
        $user = $this->userRepository->findOne($id);

        if (!$user instanceof User) {
            return response()->json(['message' => "The user with id {$id} doesn't exist"], 404);
        }

        $this->userRepository->delete($user);

        return response()->json(null, 204);
    }
}

I think this should be enough for this part. In the next part, we’re going to implement validation and Fractal Transformer.

Leave a Reply

Your email address will not be published. Required fields are marked *