Skip to content

Routing

Adarsh Kumar Maurya edited this page Dec 2, 2018 · 2 revisions

Routing

Chapter Overview

Hi everyone, welcome to chapter three. In the last chapter we derived the structure of a RESTful service, which included the URLs or the API endpoints we needed for it. It's now time to start implementing the service, and learn how Laravel supports us in the creation of such a RESTful API.

Therefore, in this chapter, we'll have a look at :

  1. How we can create routes, specifically RESTful routes in Laravel.
  2. How we can then map these routes to appropriate controller actions actually handling the incoming requests, and
  3. You will also learn how we can manage our routes in groups, and how we may apply middleware to specific routes.

Creating Routes in Laravel

Since this is no beginner level tutorial, you are probably aware with how the Laravel route works, nonetheless, here's a quick refresher.

If we compare sending an HTTP request to sending a letter, then in the case of sending a letter we, for example, want to send this letter to our grandmother and not to her neighbour. Now in order to make sure that the letter actually reaches her, and not her neighbour, we put an address on this letter. Now it's the job of the Postal Service to figure out where this address actually is, and to correctly send this letter to our grandma.

                Routing Overview

Sending a Letter          |   Sending an HTTP Request
     ---------            |          ----------
    |   You   |           |         |  Browser |
     ---------            |          ----------
         |                |               |
         |                |               |
  ----------------        |          -------------
 | Postal Service |       |         |    Router   |
  ----------------        |          -------------
              |           |           |
 -----------  ---------   |     -------------    -----------
| Grandma's || Grandma |  |    | Post        |  | Comment   |
| Neighbour ||         |  |    | Controller  |  | Controller|
 -----------  ---------         -------------    -----------

In Laravel it works pretty much the same. Now of course we're not sending a letter, but an HTTP request, but still we put an address on this request, the URL we are targeting. Now it's the job of Laravel's router to figure out that we want in this case, for example, to reach the Post Controller, and not the Comment Controller.

Now how does the Laravel router know which address maps to which post controller, or to which action there? It has no built-in address book, and it couldn't because, well, each app differs from the other apps, right?

We create this address book for Laravel. We do this by setting up our routes in a specific file. In the routes.php file, which is automatically created in our brand new Laravel installation, in this file we register all our routes using the Route facade, and then by using methods which correspond with the HTTP methods for these routes.

For example, post here, but this could of course all be GET or PATCH, whichever HTTP method we want to use.

// app/Http/routes.php
Route::post('/post',[
   'uses' => 'PostController@create',
   'as' => 'post.create',
   'middleware' => 'auth',
]);

The first argument of this method then is the path of this route. And this is appended to our domain, which together with it forms the URL we have to target, for example in the browser, to reach this route.

The second parameter of this method is an array which configures this route. For example, and very important, we may configure which controller, and there which action should actually handle the request reaching this route. We may also specify a name for this route with the as keyword, or assign some middleware, which applies to this route.

RESTful Routes

For our RESTful services we could create all our routes like this, but thankfully Laravel offers us a quicker way to do this.

Laravel, out of the box, supports RESTful routes with the resource method we can use on the Route facade.

//app/Http/routes.php
Route::resource('post', 'PostController',[
    'only' => ['index', 'show']
]);

This method also takes the path, or the key element of this path, like post for example, but then the second parameter is the controller it expects to handle these routes.

I say these routes because there's a resource method in the background creates a couple of routes. And of course, I will show these routes in the next steps. In case you're wondering why we don't specify controller action here, well of course again, because this resource method creates a couple of routes, not just one, and Laravel will expect to meet certain controller actions in this here, PostController, and I will of course also explain which actions these are on the next steps.

The third parameter of this resource method is some configuration we might add. For example, we might tell Laravel that we don't need all the default routes it would create, but only the ones to index all posts, or show a specific post in this example.

//app/Http/routes.php
Route::resource('post', 'PostController',[
    'only' => ['create', 'store', 'update', 'destroy']
]);

We might also do the opposite and specify that we want all default routes it creates, except the ones we then specify in an array here. Now, which routes does Laravel create by default? These routes here.

       RESTful Controllers in Laravel
 
     HTTP Method & URL | Controller Action
      --------------------------------------------
             GET/post  | index[show all post]
      GET/post/create  | create[get creation form]
             POST/post | store[store post on server]
       GET/post/{post} | show[get single post]
  GET/post/{post}/edit | edit[get edit form]
 PUT/PATCH/post/{post} | update[save update on server]
    DELETE/post/{post} | destroy[delete post on server]

It creates routes to get all elements, and to stay with the example here, to get all posts, to get the form, or a page, which allows us to create a new post, to store a post on the server, to get a specific single post, to get a form or a page, to edit a post, to then put or patch the actual changes we made so that we can update this post on the server, and lastly it also provides us a route to delete a resource, or delete a post in this case here.

Now here with a RESTful service, you will probably not need the GET/post/create e.g create[get creation form] and GET/post/{post} e.g edit[get edit form] routes since we will handle all our communications through the terminal, or Postman, or a third-party application, but we will probably not request any HTML pages to create and edit our resources.

Therefore this is certainly a great case to use the except keyword, and you will see this later in the demo of this tutorial.

RESTful Controllers

So great, now we've got our routes. And you also see the controller actions Laravel expects to meet with requests that reach these routes.

But how do we create the controller and the actions, by hand? We could do this, but thankfully Laravel offers us a quicker way.

We can create a RESTful controller using Laravel's Artisan command-line tool.

$ php artisan make: controller PostController --resource

There we have the make:controller command, which does exactly what the name implies, it creates a new controller, and all we pass to this command is the name of this controller, and then very important, the resource flag.

This flag will make sure that no empty controller is created, but a controller which will be prepopulated with all the actions Laravel expects with its resource route.

So by using this we make sure that all the routes created by Laravel in the background will actually find fitting controller methods.

// app/Http/Controllers/PostController.php
// ...
public function store(Request $request){
   // Controller Logic
}

For example this would create a store function, which Laravel expects to meet when getting a POST request, which wants to save a new object, and Laravel is also clever enough to automatically inject the request object into this method, so that we can access the request body and so on.

Route Grouping & Middleware

So with this we know how to create routes and controllers, but what about route grouping and middleware?

These are important issues too, right, because our routes, of course, might share some similarities. For example,

  1. They might have the same prefix, like api/v1 for version 1, 2. Some of these routes might have the same protection level, not all our routes are protected, but some of them are.

How can we easily implement this? For one, we've got route grouping, which for example, allows us to apply a prefix to all our routes in this group.

// ROUTE GROUPING

// app/Http/routes.php
Route::group(['prefix' => 'api/v1'], function(){
     Route::post('/post',[
       //Route Configuration
    ]);
 });

This will save us some time since we don't have to type this prefix for each route on its own, and very important too, if we change this prefix we only have to do this in one place, and not all over our route's file.

Now prefix is not the only criteria by which you can group, you might also group by middleware, for example. But, speaking of middleware, we may use grouping to do this, but since we used a resource method, and therefore we don't explicitly create our routes, but they are created in the background, we would have a hard time to only apply this middleware to some of these routes, since technically we only have one line of code, which we can put into this group in the route's file.

// CONTROLLER MIDDLEWARE
// app/Http/Controllers/PostController.php
public function __construct(){
    $this->middleware('auth');
    $this->middleware('log', [
        'only' => ['store', 'update']
    ]);
}

The great thing is, we can also apply middleware right in the controller, specifically in the constructor of this controller. With the middleware method, which is available in each controller, which extends Laravel space controller, we can easily apply middleware to all actions in this controller, or, if we need a more fine-grained control, as you see in the second example, we may also apply middleware only to some of these actions here.

So the assignment here works kind of similar to the resource method on the Route facade, where we could also specify with only and except, which routes we wanted to create.

Demo: Creating RESTful Routes

That has been the theory, let's see this in action. In this demo we will actually create all our RESTful routes, and the controller with the appropriate actions.

And we will group our routes and set up the required middleware. We will do this for all our API endpoints, of course. We will make sure that all our HTTP methods are supported, and of course, we will make sure that we meet the paths specified here.

I'm here inside my Laravel project, as we set it up in the very first chapter of this tutorial.

--> - Request
<-- - Response
RED - Authentication required
BLUE - No Authentication required

1. Create Meeting - RED - POST --> Title, Description, Time, UserID <-- Message, Summary, URL, Users /api/v1/meeting
2. Update Meeting - RED - PATCH --> Meeting Data, UserID, MeetingID <-- Message, Summary, URL, User /api/v1/meeting
3. Delete Meeting - RED - DELETE --> MeetingID, UserID <--Message /api/v1/meeting

4. Register - RED - POST -->UserID, MeetingID <-- Message, User, Meeting URL /api/v1/meeting/registration
5. Unregister - RED - DELETE --> Name, E-mail, Password <--Message, User, Meeting URL /api/v1/meeting/user

6. Create User - BLUE - POST --> Name, E-mail, Password <-- Message, User, MeetingURL /api/v1/meeting/user
7. Get List of all Meetings - BLUE - GET  -->(null) <-- List of Meetings, URL /api/v1/meeting
8. Get Meeting - BLUE - GET --> MeetingID <-- MeetingInfo, URL /api/v1/meeting
9. User Signin - BLUE - POST --> Email, Password <-- ??? /api/v1/user/signin

I will start with the creation of the routes, and I do this in the routes folder, and then the api.php file. If you have a look at our blueprint, you see that the meeting, as well as the meeting/registration routes can be handled with the resource method on the Route facade, since we need a couple of routes in both cases. With the only and except configuration, we will control which routes we specifically want; however, for the two user routes I will create those by hand since they don't really meet the routes Laravel would create for us, and we only need these two routes anyway.

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::resource('meeting', 'MeetingController',[
    'except' =>['edit', 'create']
]);
Route::resource('meeting/registration', 'RegistrationController',[
    'only' =>['store', 'destroy']
]);

Route::post('user',[
    'uses' => 'AuthController@store'
]);

Route::post('user/signin',[
    'uses' => 'AuthController@signin'
]);

So I'll start with the meeting routes. I start by using the Route facade, and then of course the resource method to use Laravel's help of creating multiple routes for us. Then I will specify the path, which should be /meeting, therefore I type meeting here. The second argument will be the controller, which should handle the requests reaching these routes. This will be the MeetingController here, and of course this controller doesn't exist yet, but we will create it once we're done setting up these routes. As a last argument I will specify which routes I don't need, and as explained this is done by using the except keyword, and in this example I won't need the edit and the create route, which would be used to get a form or a page which allows us to edit or create new resources, which we of course don't need in our RESTful service where we won't use HTML pages. Next, I will set up the routes to register for a meeting, and for this I will only copy this since it's pretty similar, but of course we don't have meeting, but we will have meeting/registration, and this should be handled by the RegistrationController. Here I don't need the expect keyword, but I will use the only keyword to specify which routes I do want. In this case the store route and the destroy route, so that I'm able to create and delete registrations. The last routes I will set up are the two user routes, which will be post routes, so this is the default way to create a post route of Laravel, and it will be /user to create a new user, and as configuration I will pass the controller and controller action, which should take care of our requests reaching this route, which will be the AuthController, and here the store method. And the second route will be the user/signin route, which should of course handle the sign in requests of our users, and this will also get a controller action assigned, which will also live in the AuthController, and I will use the signin action.

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::group(['prefix' => 'v1'], function(){
    Route::resource('meeting', 'MeetingController',[
        'except' =>['edit', 'create']
    ]);
    Route::resource('meeting/registration', 'RegistrationController',[
        'only' =>['store', 'destroy']
    ]);

    Route::post('user',[
        'uses' => 'AuthController@store'
    ]);

    Route::post('user/signin',[
        'uses' => 'AuthController@signin'
    ]);

});

Now if you have a look at the blueprint, we do have api/v1 in front of all our URLs. In order to set this up this way I will store all my routes here inside our route group, which is set up using the group method on this Route facade. Now I will group by prefix, so I use the prefix keyword here to then set the prefix I want to apply, api/v1 in this case, and as a second argument I will have a closure, which simply contains all the routes which should get this prefix, and in this case this of course will be all routes.

Demo: Creating RESTful Controllers

With this we've got our route set up, and we've got our route grouping going on, so now it's time to add controllers so that our requests actually are handled by something.

I do this in the command line, and make sure you navigate it into your project folder, and if you're like me here using a virtual environment, like Laravel's Homestead, make sure you're inside this environment to run all your Artisan commands. The command I will need here is, of course, php artisan make:controller MeetingController --resource to create a controller, and I will need three controllers. So I will create the MeetingController, and I will add the resource flag to make sure it is prepopulated with all the RESTful actions Laravel expects.

Adarsh:laravel-rest adarshmaurya$ php artisan make:controller MeetingController --resource
Controller created successfully.

I will repeat this step for the RegistrationController. And to finish this I will repeat this for the AuthController, but there I don't need the resource flag since I will create my actions by hand. And this is of course done because here I've got my two custom routes, and I don't use the resource method of Laravel to create the routes for me. This created our controllers, let's have a look at them.

Adarsh:laravel-rest adarshmaurya$ php artisan make:controller RegistrationController --resource
Controller created successfully.
Adarsh:laravel-rest adarshmaurya$ php artisan make:controller AuthController           
Controller created successfully.

In the Http folder, in the Controllers folder, we will see the three new files. Now the first thing I will do is in the RegistrationController, I only need the store and the destroy method, so I will get rid of all the other methods.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class RegistrationController extends Controller
{

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }
    
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

In the MeetingController, I don't need the create and the edit method, so I will delete those.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

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

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

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

And, well, the AuthController should be empty anyway, because here I will create the actions myself. I'll start by doing this, I will quickly create the store and the signin method, which I expect to reach here, as I said, these actions up in the routes file.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AuthController extends Controller
{
    public function store(Request $request){

    }

    public function signin(Request $request){

    }
}

In both actions I injected the request object, which needs this import here to work correctly, and this is done so that I can access the body of my requests once I actually take care about the data sent. Right now I don't really have something to return when we send requests to these routes, but so that we can see it works, I will return some text so that we can test it and I will just copy this here. And I will copy it for all these functions here just so, again, that we can see that it works, because of course we're going to try this out right away.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AuthController extends Controller
{
    public function store(Request $request){
        return "It works";
    }

    public function signin(Request $request){
        return "It works";
    }
}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

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


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

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        return "It works";
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        return "It works";
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        return "It works";
    }
}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class RegistrationController extends Controller
{

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

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        return "It works";
    }
}

So now I added this line in all my actions in the Meeting and RegistrationController. Now one more important thing. I talked about middleware before, and right now we don't really need to apply one because this is related to authentication, which we will handle in the last chapter of this tutorial.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class MeetingController extends Controller
{
    public function __construct()
    {
        // $this->>middleware('name');
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return "It works";
    }


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

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        return "It works";
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        return "It works";
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        return "It works";
    }
}

However, what I will do is I will set up the constructor already so that we are ready to go once we need it. So, I'll add the constructor with the __construct function here, and here we will then specify middleware like this, this-middleware, and then just the name of the middleware, and possibly some additional configuration, like I showed it on the slides. As I just said, I will come back to this in the last chapter of this tutorial.

Demo: Testing & Laravel's CSRF Protection

With this we're good to go, and we should be able to test. And as explained in the first chapter, I will use Postman to test it. Now depending on your setup, you may just use localhost as your domain here, but since I use my virtual machine with Vagrant and Homestead, I've assigned a URL to this route, which is laravel-rest.dev(localhost), but in the end this will result in the same if you're developing on your machine and have localhost there. So I enter the route to get all meetings, and let's see what happens if I click Send. This works. Does it also work if I add an ID here? It also works. What if I send a post request there, which I would do if I created a new meeting? As you can see, this throws us an error, the TokenMismatchException. And this is because, by default, Laravel will check for the CsrfToken, which is a security measure implemented by Laravel. However, we don't want this checked when using a RESTful service, and in order to fix this I will go to the Providers folder in my app folder here, and there the RouteServiceProvider.php file is the one I'm looking for. In this file I have at the very bottom of it, a setup, which sets up that each route will get this web middleware applied, and this web middleware includes, amongst other things we won't need here, the CSRF protection. Now for a RESTful service we won't need this web middleware, and therefore I can securely get rid of this. Now if I save this, and try this request again, you see that it now works. To try out a patch request I will again enter an ID of the meeting we want to patch, and this works too. Now, if I want to delete one, this also works, and you will see that the same is true if I try to, registration, to delete a registration, or to create a new one. Lastly, let's check the user routes. Great. So with this we set up the routing of our application, and this is the very important first step. As you saw it was quite easy with Laravel's help. And in the next chapter, we will dive deeper into this, we will actually handle requests received by these actions, and we will make sure that we send back more, or better responses than just a string saying, it works.