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:
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:
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:
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.
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.
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"];