How to make a REST API first web application in Laravel

33,550

Solution 1

You should utilize the Repository / Gateway design pattern: please see the answers here.

For example, when dealing with the User model, first create a User Repository. The only responsibility of the user repository is to communicate with the database (performing CRUD operations). This User Repository extends a common base repository and implements an interface containing all methods you require:

class EloquentUserRepository extends BaseRepository implements UserRepository
{
    public function __construct(User $user) {
        $this->user = $user;
    }


    public function all() {
        return $this->user->all();
    }

    public function get($id){}

    public function create(array $data){}

    public function update(array $data){}

    public function delete($id){}

    // Any other methods you need go here (getRecent, deleteWhere, etc)

}

Then, create a service provider, which binds your user repository interface to your eloquent user repository. Whenever you require the user repository (by resolving it through the IoC container or injecting the dependency in the constructor), Laravel automatically gives you an instance of the Eloquent user repository you just created. This is so that, if you change ORMs to something other than eloquent, you can simply change this service provider and no other changes to your codebase are required:

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider {

    public function register() {
        $this->app->bind(
            'lib\Repositories\UserRepository',        // Assuming you used these
            'lib\Repositories\EloquentUserRepository' // namespaces
        );
    }

}

Next, create a User Gateway, who's purpose is to talk to any number of repositories and perform any business logic of your application:

use lib\Repositories\UserRepository;

class UserGateway {

    protected $userRepository;

    public function __construct(UserRepository $userRepository) {
        $this->userRepository = $userRepository;
    }

        public function createUser(array $input)
        {
            // perform any sort of validation first
            return $this->userRepository->create($input);
        }

}

Finally, create your User web controller. This controller talks to your User Gateway:

class UserController extends BaseController 
{
    public function __construct(UserGatway $userGateway)
    {
        $this->userGateway = $userGateway;
    }

    public function create()
    {
        $user = $this->userGateway->createUser(Input::all());

    }
}

By structuring the design of your application in this way, you get several benefits: you achieve a very clear separation of concerns, since your application will be adhering to the Single Responsibility Principle (by separating your business logic from your database logic) . This enables you to perform unit and integration testing in a much easier manner, makes your controllers as slim as possible, as well as allowing you to easily swap out Eloquent for any other database if you desire in the future.

For example, if changing from Eloquent to Mongo, the only things you need to change are the service provider binding as well as creating a MongoUserRepository which implements the UserRepository interface. This is because the repository is the only thing talking to your database - it has no knowledge of anything else. Therefore, the new MongoUserRepository might look something like:

class MongoUserRepository extends BaseRepository implements UserRepository
{
    public function __construct(MongoUser $user) {
        $this->user = $user;
    }


    public function all() {
        // Retrieve all users from the mongo db
    }

    ...

}

And the service provider will now bind the UserRepository interface to the new MongoUserRepository:

 $this->app->bind(
        'lib\Repositories\UserRepository',       
        'lib\Repositories\MongoUserRepository'
);

Throughout all your gateways you have been referencing the UserRepository, so by making this change you're essentially telling Laravel to use the new MongoUserRepository instead of the older Eloquent one. No other changes are required.

Solution 2

You should be use Repository for this design.

Example -

//UserRepository Class
class UserRepository {
    public function getById($id)
    {
      return User::find($id);
    }
}

// WebUser Controller
class WebUserController extends BaseController {
    protected $user;

    public function __construct(UserRepository $user)
    {
        $this->user = $user;
    }

    public function show($id)
    {
        return View::make('user.view')->with('data', $this->user->getById($id));
    }
}

// APIUser Controller
class UserController extends BaseController {
    protected $user;

    public function __construct(UserRepository $user)
    {
        $this->user = $user;
    }

    public function show($id)
    {
        $data =>$this->user->getById($id);
        return Response::json(array('success'=>true,'user'= $data->toArray()));
    }
}

Solution 3

Checkout Laravel's RESTful controllers:

http://laravel.com/docs/controllers#restful-controllers

Their docs do a pretty good job.

But even better is this tutorial:

http://code.tutsplus.com/tutorials/laravel-4-a-start-at-a-restful-api-updated--net-29785

Solution 4

This is a video by Jeffrey Way he is one of the better Laravel developers. In this tutorial he is connecting a BackboneJS application to a RESTful service that he sets up in Laravel. It doesn't get any better then this. I can write you a lot of boilerplate, but just learn it by watching a nice video and having a coffee. ;)

https://www.youtube.com/watch?v=uykzCfu1RiQ

Solution 5

I have a response to the problem you are having with the Response. You can get the headers, status code and data from the Response.

// your data
$response->getData();

// the status code of the Response
$response->getStatusCode(); 

// array of headers
$response->headers->all();

// array of headers with preserved case
$response->headers->allPreserveCase(); 

$response->headers is a Symfony\Component\HttpFoundation\ResponseHeaderBag which inherits from Symfony\Component\HttpFoundation\HeaderBag

Share:
33,550
Martin Taleski
Author by

Martin Taleski

IT enthusiast

Updated on December 25, 2020

Comments

  • Martin Taleski
    Martin Taleski over 3 years

    I want to make an API first application in Laravel. I don't know what is the best approach to do this, I will explain what I am trying to do, but please feel free to give answers how to do this in a different way.

    I don't want all my frontend to be written in javascript and parse the JSON output of the API with angular.js or something similar. I want my Laravel application to produce the HTML views. I am trying to go down the road of having two controllers one on for the API and one for the web. For the show User action my routes.php looks like this:

    # the web controller
    Route::controller('user', 'WebUserController');
    
    # the api controller 
    Route::group(array('prefix' => 'api'), function() {
        Route::resource('user', 'UserController');
    });
    

    So /user will take me to WebUserController and /api/user will take me to the UserController. Now I want to put all my logic in the API UserController, and call its actions from the WebUserController. Here is the code for both of them:

    class UserController extends BaseController 
    {
        public function show($id)
        {
            $user = User::find($id);
            return Response::json(array('success'=>true,'user'=>$user->toArray()));
        }
    }
    
    class WebUserController extends UserController 
    {
        public function getView($id) 
        {
             # call the show method of the API's User Controller
             $response =  $this->show($id);
             return View::make('user.view')->with('data', $response->getData());
        }
    }
    

    In the WebUserController I am able to get the json content of the response with getData(), but I am not able to get the headers and status code (they are protected properties of Illuminate\Http\JsonResponse).

    I think that my approach might not be the best, so I am open to suggestions how to make this app.

    EDIT: The question how to get the headers and status of the response has been answered by Drew Lewis, but I still think that there might be a better way how to design this

  • Martin Taleski
    Martin Taleski about 10 years
    I've been through that tutorial... It does not answer my question how to keep the logic in the API controllers, and create separate controllers/views for the web app that will call the API controllers
  • Martin Taleski
    Martin Taleski about 10 years
    yeah that fetches all the stuff I need. What about the approach in general... can you think of a better idea?
  • lagbox
    lagbox about 10 years
    @martin im thinking of a way to just have 1 controller for both. Build your data for the response and pass it to an intermediate that based upon if it is from api route or not will return the correct response. but it depends upon how different you expect a Api@show to be from a Web@getView, in terms of the data.
  • Martin Taleski
    Martin Taleski about 10 years
    this looks fine and simple, with only one extra class per model.
  • Martin Taleski
    Martin Taleski about 10 years
    thanks, this is a more complex design than Nyan's answer... can you please explain what is the benefits. For example if you are to change Eloquent for another ORM, you would need to change the EloquentUserRepository, but also all the Gateways.
  • Martin Taleski
    Martin Taleski about 10 years
    hmm, this one seems to show how to make a backend json api in laravel with a frontend in backbone, which is not exactly what I am looking for.
  • sidneydobber
    sidneydobber about 10 years
    Just use json_decode() and you have a php array and ignore it's for BackboneJS. It's RESTful so it doesn't matter what calls the API. The fact that it returns JSON is awesome because any language can process JSON!
  • seeARMS
    seeARMS about 10 years
    Please re-read my answer - I added some more information. You don't need to change any gateways if changing to another ORM, you only need to change the service provider and repository.
  • malhal
    malhal almost 10 years
    Too much boiler plate just for an unlikely scenario of changing ORMs though.
  • user361697
    user361697 almost 10 years
    Hi, I have made a template based on the suggestions. One thing I couldnt figure out was how to handle validations. Can you please suggest how to go about integrating validation service with it ? Code : github.com/octabrain/Laravel4-Patterns
  • user361697
    user361697 almost 10 years
    Finally managed to put all the pieces together. Code github.com/octabrain/Laravel4-Patterns
  • Rafael Bugajewski
    Rafael Bugajewski about 9 years
    I don’t see the point in letting Laravel convert PHP arrays to JSON strings and then to manually decode it back in your own code.
  • Pedro Moreira
    Pedro Moreira over 8 years
    How would you go about relationships? Imagine that I want to create a document, which has some relations like creator, customer and rows. Would you pass those relations to the $documentRepository->create() method? Assume that at least creator and customer can't be null.