feature: api for user registration

This commit is contained in:
kusowl 2026-02-20 13:11:25 +05:30
parent 138a0ba4bf
commit 4397f91b65
15 changed files with 392 additions and 2 deletions

View File

@ -0,0 +1,33 @@
<?php
namespace App\Actions;
use App\Data\RegisterDTO;
use App\Models\User;
final readonly class CreateUserAction
{
/**
* Rather than using facades, we use DI ( Dependency Injection ).
* That's why User model is injected and new user is created by
* set the properties rather that model's static `create()` method.
*
* This will create problems if using Octane ot FrankenPHP.
*/
public function __construct(private User $user) {}
public function execute(RegisterDTO $dto): User
{
$this->user->email = $dto->email;
$this->user->name = $dto->name;
$this->user->city = $dto->city;
$this->user->mobile_number = $dto->mobileNumber;
// We already have casts to auto hash passwords.
$this->user->password = $dto->password;
$this->user->save();
return $this->user;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Contracts;
use Illuminate\Foundation\Http\FormRequest;
interface InputDataTransferObject
{
public static function fromRequest(FormRequest $request): self;
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Contracts;
interface OutputDataTransferObject
{
public function toArray(): array;
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Data;
use App\Contracts\InputDataTransferObject;
use Illuminate\Foundation\Http\FormRequest;
final class RegisterDTO implements InputDataTransferObject
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly string $mobileNumber,
public readonly string $password,
public readonly string $city,
) {}
public static function fromRequest(FormRequest $request): InputDataTransferObject
{
return new self(
name: $request->validated('name'),
email: $request->validated('email'),
mobileNumber: $request->validated('mobile_number'),
password: $request->validated('password'),
city: $request->validated('city')
);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers;
use App\Actions\CreateUserAction;
use App\Data\RegisterDTO;
use App\Http\Requests\RegisterUserRequest;
use Illuminate\Http\JsonResponse;
class RegisteredUserController extends Controller
{
public function __invoke(RegisterUserRequest $request, CreateUserAction $action): JsonResponse
{
$action->execute(RegisterDTO::fromRequest($request));
return response()->json(['message' => 'User Registration Successfull']);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;
class RegisterUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'min:3', 'max:50'],
'password' => ['required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols()],
'email' => 'required|string|email|max:255|unique:users',
'city' => 'required|string|min:2|max:255',
'mobile_number' => 'required|min:10|max:25',
];
}
}

View File

@ -21,6 +21,8 @@ class User extends Authenticatable
'name',
'email',
'password',
'city',
'mobile_number',
];
/**

View File

@ -7,11 +7,12 @@
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
$middleware->statefulApi();
})
->withExceptions(function (Exceptions $exceptions): void {
//

View File

@ -11,6 +11,7 @@
"require": {
"php": "^8.2",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.10.1"
},
"require-dev": {

65
backend/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "199095e97ebcedc029e82d15eaea77fa",
"content-hash": "ed4c85d2742e86605a289e878290b8a3",
"packages": [
{
"name": "brick/math",
@ -1333,6 +1333,69 @@
},
"time": "2026-02-06T12:17:10+00:00"
},
{
"name": "laravel/sanctum",
"version": "v4.3.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
"reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/console": "^11.0|^12.0|^13.0",
"illuminate/contracts": "^11.0|^12.0|^13.0",
"illuminate/database": "^11.0|^12.0|^13.0",
"illuminate/support": "^11.0|^12.0|^13.0",
"php": "^8.2",
"symfony/console": "^7.0|^8.0"
},
"require-dev": {
"mockery/mockery": "^1.6",
"orchestra/testbench": "^9.15|^10.8|^11.0",
"phpstan/phpstan": "^1.10"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Sanctum\\SanctumServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Sanctum\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
"keywords": [
"auth",
"laravel",
"sanctum"
],
"support": {
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2026-02-07T17:19:31+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.9",

34
backend/config/cors.php Normal file
View File

@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

View File

@ -0,0 +1,84 @@
<?php
use Laravel\Sanctum\Sanctum;
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort(),
// Sanctum::currentRequestHost(),
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. This will override any values set in the token's
| "expires_at" attribute, but first-party sessions are not affected.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Token Prefix
|--------------------------------------------------------------------------
|
| Sanctum can prefix new tokens in order to take advantage of numerous
| security scanning initiatives maintained by open source platforms
| that notify developers if they commit tokens into repositories.
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
*/
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable()->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('mobile_number');
$table->string('city');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('user', function (Blueprint $table) {
$table->dropColumn('mobile_number', 'city');
});
}
};

13
backend/routes/api.php Normal file
View File

@ -0,0 +1,13 @@
<?php
use App\Http\Controllers\RegisteredUserController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
Route::middleware('guest')->group(function () {
Route::post('/register', RegisteredUserController::class);
});