Courses

Build Laravel API for Car Parking App: Step-By-Step

User Area: Register New User

Now, let's create our first API endpoint. To manage their parking and vehicles, of course, we need to have users. Their DB structure and model come by default with Laravel, we "just" need to create the endpoints for them to register and then log in.

The whole Auth mechanism will depend also on the client that would use the app: it may be different for JS-based web clients, mobile apps, or other consumers. But, in our case, we will use Laravel Sanctum for the authentication, with its generated tokens.

Generally, there are boilerplates and starter kits for Laravel projects, like Laravel Breeze or Jetstream. But, in our case, we need only the API, without any web functionality, so we will not use any of them directly.

Let's create the first endpoint: GET /api/v1/auth/register.

For that, we create a Controller:

php artisan make:controller Api/V1/Auth/RegisterController

I also recommend immediately starting to store things in their own subfolders and namespaces. That's why we have:

  • Api subfolder to specify that it's an API Controller
  • V1 subfolder to specify that it's Version 1 of the API (more on that below)
  • Auth subfolder to specify that it's a Controller related to Auth

Of course, there are other ways to name the Controllers, like the general AuthController with a few methods, feel free to choose your structure.

For now, let's create the method __invoke() in this Controller, as it will be a Single Action Controller. Again, you may do it differently, if you wish.

app/Http/Controllers/Api/V1/Auth/RegisterController.php:

namespace App\Http\Controllers\Api\V1\Auth;
 
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
 
class RegisterController extends Controller
{
public function __invoke(Request $request)
{
// ... will fill that in a bit later
}
}

Next, let's create a Route for this Controller.

routes/api.php:

use \App\Http\Controllers\Api\V1\Auth;
 
// ...
 
Route::post('auth/register', Auth\RegisterController::class);

By the way, did you know that you can use a namespace on top, not just the specific Controller?

Here, we don't need to specify the method name. We can reference a full Controller class, because it's a Single Action "invokable" controller, as I already mentioned.

Now, versioning. Where does that /api/v1 comes from automatically? Why don't we specify it in the Routes file? Generally, I recommend always versioning your APIs: even if you're not planning future versions, for now, it's a good practice to start with "v1".

To "attach" your routes/api.php file to the automatic prefix of /api/v1, you need to change the logic in the app/Providers/RouteServiceProvider.php file:

class RouteServiceProvider extends ServiceProvider
{
public function boot()
{
$this->configureRateLimiting();
 
$this->routes(function () {
Route::middleware('api')
// CHANGE HERE FROM 'api' to 'api/v1'
->prefix('api/v1')
->group(base_path('routes/api.php'));
 
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
}

And that's it for the routing. Now we can try to make a POST request to the registration. It should return 200 but do nothing. But hey, no error, it works!

This is how it looks in my Postman client:

Laravel API Postman

All we need to do now is "just" implement the registration.

This is how our Controller will look like:

app/Http/Controllers/Api/V1/Auth/RegisterController.php:

namespace App\Http\Controllers\Api\V1\Auth;
 
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
 
class RegisterController extends Controller
{
public function __invoke(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Password::defaults()],
]);
 
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
 
event(new Registered($user));
 
$device = substr($request->userAgent() ?? '', 0, 255);
 
return response()->json([
'access_token' => $user->createToken($device)->plainTextToken,
], Response::HTTP_CREATED);
}
}

As you can see, we're validating the data directly in the Controller, expecting those fields:

  • name
  • email
  • password
  • password_confirmation (the validation rule "confirmed")

In case of validation errors, if you provide "Accept: application/json" in the header, the result will be returned like this, with the HTTP status code 422.

Laravel API Postman

Of course, you may prefer to validate the data separately, in a Form Request class, and that's totally fine.

If the registration is successful, we generate the new Laravel Sanctum token and return it.

The idea is that all the other requests are being done by passing that token as a Bearer token, we'll get to that in a minute.

Laravel API Postman

Also, as you can see, we have a $device variable, coming automatically from the User Agent, so we're creating a token specifically for that front-end device, like a mobile phone.

Have you noticed the Response::HTTP_CREATED? I personally like to use those constants that define HTTP Status codes in a human-friendly way, so I will use them throughout this course. They come from Symfony, and the most widely used one are these:

const HTTP_OK = 200;
const HTTP_CREATED = 201;
const HTTP_ACCEPTED = 202;
const HTTP_NO_CONTENT = 204;
const HTTP_MOVED_PERMANENTLY = 301;
const HTTP_FOUND = 302;
const HTTP_BAD_REQUEST = 400;
const HTTP_UNAUTHORIZED = 401;
const HTTP_FORBIDDEN = 403;
const HTTP_NOT_FOUND = 404;
const HTTP_METHOD_NOT_ALLOWED = 405;
const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
const HTTP_UNPROCESSABLE_ENTITY = 422;

Finally, we're firing a general Laravel Auth event, that could be caught with any Listeners in the future - this is done by Laravel Breeze and other starter kits by default, so I suggest doing that as well.

Also, one more thing that I will change in the Laravel config for the API to be consumed by future Vue/React or mobile apps, is allow to login with credentials. It won't give any benefit on the back-end, but just preparing for the future tutorials :)

config/cors.php:

return [
// ...
 
'supports_credentials' => true, // default value "false"
];
avatar

Povilas, any reason why you're using substr() for the $device variable?

$device = substr($request->userAgent() ?? '', 0, 255);

avatar

Well, it's a silly reason, but device field is varchar with 255 symbols in DB, from what I remember :) I have a video about it on YouTube: https://www.youtube.com/watch?v=8arHScWrlK4

avatar

Good shout with the status codes - I always added the manually but this is so much better!

👍 2
🥳 1
👀 1
😍 1
avatar

Povilas, would you mind extending the tutorial more on how we can store the device ID to our DB and sending a firebase notification to that specific device ID

avatar

Saving the device id to the database is trivial you can just create a Device model and you use an insert query, however firebase is not trivial and probably beyond the scope of this tutorial

avatar

Michael, I don't actively work with Firebase so I can't make a tutorial about it, at the moment.

avatar

Sorry but having a hard time following along I keep getting Error cods that do not make sense! The GET method is not supported for route api/v1/auth/register. Supported methods: POST. Postman 405 Method Not Allowed [http://project6.lcl/api/v1/auth/register] I feel I am missing something Can you help? Liked video courses much better PS I am using PHP Storm with Laravel 10.1.2 & php 8.2

avatar

Why do you make a GET request? It should be POST. It's Route::post() in the routes.

avatar

I just copyed and pasted what you had!

avatar

The code is copied, yes, but it's also in the way you make the request in the Postman. You should choose the POST method there, not GET.

avatar

the error is quite obvious if I'm honest, besides you'd never use a GET request on any route that does registration for security reasons

avatar
fatima zohra ezzaidani

Please I don't get well , why using event(new Registered($user)); why we need to listenning in a new registration? , in this case?

thank you

avatar

This code comes from examples in Laravel Starter kits like Breeze, it's more like "just in case" if someone wants to perform some actions on registration. Also it allows to easily enable email verification if needed.

avatar
fatima zohra ezzaidani

ok thank you

avatar

If I send the POST request with record data it creates the record in the database and returns 201. However, if I send the empty POST request it returns 200 and the laravel presentation web in HTML instead of the JSON response with the required fields and the 422 code.

Why does this happen?

avatar

i guess maybe you forget to set HTTP Header, the Accept key should be set to application/json. CMIIW

avatar

Can someone throw more light on 'supports_credentials' => true usecase.

avatar
Muhammad Tanvir Hasan

https://www.stackhawk.com/blog/laravel-cors/

avatar

The routing part doesn't looks corret to me, shouln't "v1" preffix be added in routes/api.php: on top of that one added from RouteServiceProvider ?

Also imports routes/api.php could be simplified, and we could have:

Route::prefix('/v1/')->namespace('\App\Http\Controllers\v1')->group(function () {
    Route::apiResource('something', SomethingController::class);
});

Route::prefix('/v2/')->namespace('\App\Http\Controllers\v2')->group(function () {
    Route::apiResource('something', SomethingElseController::class);
});
avatar

that's a personal preference, prefix is already added in route service provider

$this->routes(function () {
    Route::middleware('api')
        // CHANGE HERE FROM 'api' to 'api/v1'
        ->prefix('api/v1')
        ->group(base_path('routes/api.php'));

you can always update things if you ever going to have v2

avatar

Not quite a perosnal prefernce. Idea behind versioning APIs is to have more then one version of an API at the same time. Let's say that you have quite complex API and several apps using it. To avoid adding breaking changes you develop v2 of the API. Because v1 is still online and intact, you can migrate to the new API all apps one by one, leave some using v1, or use v2 for newly developed apps leaving old on v1. The change you mentioned is no different then using just prefix('api').

avatar

It is a personal preference where to put that v1 - in routes file, or in service provider.

If you have a v2, then you add another Route::group there, something like:

$this->routes(function () {
    Route::middleware('api')
        ->prefix('api/v1')
        ->group(base_path('routes/api.php'));

    Route::middleware('api')
        ->prefix('api/v2')
        ->group(base_path('routes/api_v2.php'));
avatar

Ahh thank you both guys, I missed fact that it's possible to add more then prefix to the route and create extra file that will contain routes valid for given prefix.