DEV Community

loading...

Creating your first API with Laravel in the proper way

victoor profile image Víctor Falcón Updated on ・5 min read

Starting with Laravel is easy and creating your first endpoint will take you just a few minutes. But, if you want to do it in the proper way to get a reusable, scalable and testable code you must read this before starting.

👉 Post disponible en español 🇪🇸


Before starting, just let you know that I had made a video talking about this with a step-by-step guide showing how to refactor your code without breaking anything and, this way, getting a more clean code. (In 🇪🇸 Spanish)


Ok! Let's start with this post.

Usually, an endpoint in Laravel have three parts:

  1. The firs block of code in our controller is to validate the incoming request or getting some model from the URL.
  2. Next we do something with this data, for example, creating a user, sending an email, or whatever.
  3. Finally, we give to our API client a response in JSON.

This is a usual structure of an endpoint and a bad endpoint have all this part in the same controller file making all the code not reusable so, taking this into account, we are going to make our endpoint step-by-step.

TOC

Validate the request

In Laravel, we have a lot of different ways to use the validator class and check the entry data from the request but the best you can do, on a controller, it's to use a custom request object.

You can create this by simply run artisan make:request UserStoreRequest. This will create the file app/Http/Requests/UserStoreRequest.php with a content similar to:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserStoreRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [];
    }
}

Enter fullscreen mode Exit fullscreen mode

As you can see, we have two main methods: one to check if the current user is authorized to perform this action or not.

You can use Auth() class here to get current user and check permissions if needed.

And the other method is used to pass validation rules to the request.

With that, you can use this class in your controller and the input data gets validated.

<?php

namespace App\Http\Controllers;

use App\Http\Request\UserStoreRequest;

class UserController extends Controller
{
    /**
     * Store a new user.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(UserStoreRequest $request)
    {
        $valid = $request->validated();

        //
    }
Enter fullscreen mode Exit fullscreen mode

Creating application services

When all our input data is validated, and we have an expected and valid request we have to do something with that data.

A bad practice it's to work with this data inside the controller. In our controller we have code that belongs to the infrastructure layer and this use cases must be in our application layer in order to make this code reusable and easy to test.

So, my recommendation it's to create a service inside the folder app/Services. Something like this:

<?php

declare(strict_types=1);

namespace App\Services;

use App\Mail\UserSignUpMail;
use App\Models\User;

final class UserStoreService
{
    public function __invoke(
        string $email, 
        string $name, 
        string $password
    ): User {
        $password = \Hash::make($password);

        return User::create([
            'email' => $email,
            'name' => $name,
            'password' => $password
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we are only hashing the password and creating the user in the invoke method. But, doing this in a service file instead of doing inside the controller allow us to write a simple unit test to check that the service it's doing what we expect and also, we can reuse this code and calling it from another place, not only the controller.

For example, if we want to create a user from a Laravel Command we can use this same service and trigger the same use case without changing anything in our service code.

In our controller we just need to call this new service.

<?php

namespace App\Http\Controllers;

use App\Http\Request\UserStoreRequest;

class UserController extends Controller
{
    private UserStoreService $storer;

    public function __construct(UserStoreService $storer)
    {
        $this->storer = $storer;
    }

    /**
     * Store a new user.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(UserStoreRequest $request)
    {
        $user = ($this->storer)(
            $request->get('email'),
            $request->get('name'),
            $request->get('password')
        );
    }
Enter fullscreen mode Exit fullscreen mode

Dispatching events

As you can see, our service it's really simple, but in the real world that will never happen. Usually, when we create a user we must send an email, we need to create some configurations, send some notifications or whatever and that's why we should use events and avoid overcomplicating our service.

If we use events we can listen to this event and trigger different actions in different parts of our code to keep each service and listener as simple as possible.

In Laravel, it's really simple to trigger some events and listen to them and trigger some actions when needed.

The first thing we need it's to create our event with artisan make:event UserCreated. This event will receive the created user in the constructor.

Something like this:

<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserCreated
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}
Enter fullscreen mode Exit fullscreen mode

And then, we create our listener to send a welcome email to the user after creating it. In our console we should run artisan make:listener SendWelcomeMail --event=UserCreated and inside the handle method we send our custom email.

And finally, you need to register the event in your User model in order to trigger it automatically every time a new user it's created.

<?php

// ...

class User extends Authenticable
{
    // ...

    protected $dispatchesEvents = [
        'created' => UserCreated::class,
    ];
}
Enter fullscreen mode Exit fullscreen mode

Remember that you also need to register the event and the listner in your EventServiceProvider. You have more information inside Laravel documentation

Formatting the response

The last part of a good controller just need to get the result of the service we created before (if we are returning something) and transforming it in a valid JSON.

Usually, in Laravel, I found a lot of application that are just returning a model. This works, because Laravel can transform this models into a valid JSON but, if we have something else than a really simple API this it's not a good idea.

The problem of doing this is that we are not customizing or defining anything and Laravel it's returning our model with all fields all the time.

If you need more control, and you are going to need this sometime, you will need to create and return and API resource.

This API resources let you to customize the response by adding conditional fields but also relations, pagination and metadata.

This is the best you can do if you plan to create a good API full of services, endpoint and different kind of responses.

Discussion (5)

Collapse
martinbean profile image
Martin Bean

You can use Auth() class here to get current user and check permissions if needed.

You can also just use $this->user() since you’re in a form request and it extends Laravel’s Request class.

You can also drop the authorize method if all you do is return true. Form requests will only invoke it if it exists; otherwise it’ll just let the request through.

Your “user store service” is also more like a command or “action” rather than a user service class. If you resolve it from the container, you should inject the hasher instance instead of relying on a facade again so you can easily test it without the framework needing to be booted:

use Illuminate\Contracts\Hashing\Hasher;

class UserStoreService
{
    protected $hasher;

    public function __construct(Hasher $hasher)
    {
        $this->hasher = $hasher;
    }

    public function __invoke(string $email, string $name, string $password): User
    {
        $password = $this->hasher->make($password);

        return User::create([
            'email' => $email,
            'name' => $name,
            'password' => $password
        ]);
    }
}
Enter fullscreen mode Exit fullscreen mode
public function testItCanCreateUsers(): void
{
    $hasher = Mockery::mock(Hasher::class);

    $service = new StoreUserService($hasher);

    // Now test service class
}
Enter fullscreen mode Exit fullscreen mode
Collapse
victoor profile image
Víctor Falcón Author

Good points! Usually I like to use facades because they are really easy to test with Laravel and it's something common, but it's true that you need to load the framework for that. 😉

Collapse
omarizem profile image
IZEM

Great article, can you write another article like that about jobs & emails ?

Collapse
fininhors profile image
Francisco Junior

Excellent article, one of the best I've read so far about API development in Laravel. Will it continue? Hope so. Great job, congratulations.

Collapse
victoor profile image
Víctor Falcón Author

Thanks a lot for your comment!

What else do you want to know about API development in Laravel?

Forem Open with the Forem app