Passport authentication using access token demo in Laravel

Create authentication APIs: Login, Access token, Logout, Update user info, OTP API

In this post, we are going to build a simple authentication JSON APIs in Laravel. There will be five JSON APIs.

  1. Send OTP API: /api/account/send-otp/ (for sending OTP for given mobile number)
  2. Login API: /api/account/login/ (For verifing OTP)
  3. Access Token API: /api/account/refresh-access-token/ (For requesting access token for authenticated user)
  4. Update user info: /api/account/update-user/ (for updating user information on the server)
  5. Logout API: /api/account/logout/ (for making authentication token invalid)

You can find code on GitHub here https://github.com/28harishkumar/laravel_auth_demo

Create a new Project

If you have not installed Composer then install it from https://getcomposer.org/ and install Laravel as mentioned here https://laravel.com/docs/8.x/installation#installing-laravel. Create a new project named `laravel_authentication` by using:

  1. laravel new laravel_authentication

For Oauth2, we will using Laravel Passport. Install Laravel Passport using:

  1. composer require laravel/passport

Now setup database by updating laravel_authentication/.env file.

  1. DB_CONNECTION=mysql
  2. DB_HOST=127.0.0.1
  3. DB_PORT=3306
  4. DB_DATABASE=your_database_name
  5. DB_USERNAME=your_databasee_username
  6. DB_PASSWORD=your_mysql_password

Database Tables (Migrations)

For authentication we need two table User and OTP. Some more tables will be created by passport package to handle access tokens. Open laravel_authentication/database/migrations/2014_10_12_000000_create_users_table.php and replace up() function with following code:

  1. <?php // don't include this line, this is for code decoration
  2. public function up()
  3. {
  4. Schema::create('users', function (Blueprint $table) {
  5. $table->id();
  6. $table->string('name')->nullable();;
  7. $table->string('email')->nullable();
  8. $table->string('mobile_no')->unique();
  9. $table->timestamp('email_verified_at')->nullable();
  10. $table->string('password')->nullable();;
  11. $table->string('photo')->nullable();;
  12. $table->rememberToken()->default(1);
  13. $table->timestamps();
  14. });
  15. }

In User table we have marked name and email nullable and added mobile_no and photo fields. Now Create OTP table using

  1. php artisan make:migration create_otp_table

Replace content of laravel_authentication/database/migrations/*_create_otp_table.php with:

  1. <?php
  2. use Illuminate\Database\Migrations\Migration;
  3. use Illuminate\Database\Schema\Blueprint;
  4. use Illuminate\Support\Facades\Schema;
  5. class CreateOtpTable extends Migration
  6. {
  7. /**
  8. * Run the migrations.
  9. *
  10. * @return void
  11. */
  12. public function up()
  13. {
  14. Schema::create('otp', function(Blueprint $table)
  15. {
  16. $table->id();
  17. $table->string('mobile_no');
  18. $table->string('code');
  19. $table->boolean('active')->default(1);
  20. $table->timestamps();
  21. });
  22. }
  23. /**
  24. * Reverse the migrations.
  25. *
  26. * @return void
  27. */
  28. public function down()
  29. {
  30. //
  31. {
  32. Schema::drop('otp');
  33. }
  34. }
  35. }

Now migrate the tables to the database using:

  1. php artisan migrate

Now we need to generate keys for passport. Run the following command in the terminal:

  1. php artisan passport:install

Models

For using Eloquent ORM, we need to define Model classes for User and OTP tables. Open laravel_authentication/app/Models/User.php and replace the code with:

  1. <?php
  2. namespace App\Models;
  3. use Laravel\Passport\HasApiTokens;
  4. use Illuminate\Contracts\Auth\MustVerifyEmail;
  5. use Illuminate\Database\Eloquent\Factories\HasFactory;
  6. use Illuminate\Foundation\Auth\User as Authenticatable;
  7. use Illuminate\Notifications\Notifiable;
  8. class User extends Authenticatable
  9. {
  10. use HasApiTokens, HasFactory, Notifiable;
  11. /**
  12. * The attributes that are mass assignable.
  13. *
  14. * @var array
  15. */
  16. protected $fillable = [
  17. 'name',
  18. 'mobile_no',
  19. 'password',
  20. ];
  21. /**
  22. * The attributes that should be hidden for arrays.
  23. *
  24. * @var array
  25. */
  26. protected $hidden = [
  27. 'password',
  28. 'remember_token',
  29. ];
  30. /**
  31. * The attributes that should be cast to native types.
  32. *
  33. * @var array
  34. */
  35. protected $casts = [
  36. 'email_verified_at' => 'datetime',
  37. ];
  38. }

In line 5 and line 13, I have added HasApiTokens from Laravel Passport for using Oauth2. We will use createToken function from HasApiTokens to generate access token for the user.

Now create a new folder in Models named Account with a file OTP.php in it. Paste the following code in laravel_authentication/app/Models/Account/OTP.php

  1. <?php
  2. namespace App\Models\Account;
  3. use Carbon\Carbon;
  4. use Illuminate\Database\Eloquent\Factories\HasFactory;
  5. use Illuminate\Database\Eloquent\Model;
  6. use Illuminate\Support\Facades\Http;
  7. class OTP extends Model
  8. {
  9. use HasFactory;
  10. protected $table = 'otp';
  11. public function isValidOTPTime() {
  12. // valid only for 10 minutes
  13. $timenow = Carbon::now();
  14. return $timenow->diffInMinutes($this->created_at) < 10;
  15. }
  16. /**
  17. * Sends request for OTP
  18. */
  19. public function sendToMobile() {
  20. // MSG19 server request
  21. $curl = curl_init();
  22. curl_setopt_array($curl, array(
  23. CURLOPT_URL => "https://api.msg91.com/api/v5/flow/",
  24. CURLOPT_RETURNTRANSFER => true,
  25. CURLOPT_ENCODING => "",
  26. CURLOPT_MAXREDIRS => 10,
  27. CURLOPT_TIMEOUT => 30,
  28. CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  29. CURLOPT_CUSTOMREQUEST => "POST",
  30. CURLOPT_POSTFIELDS => "",
  31. CURLOPT_SSL_VERIFYHOST => 0,
  32. CURLOPT_SSL_VERIFYPEER => 0,
  33. CURLOPT_HTTPHEADER => array(
  34. "authkey: EnterYourAuthKey",
  35. "content-type: application/json"
  36. ),
  37. ));
  38. $response = curl_exec($curl);
  39. $err = curl_error($curl);
  40. curl_close($curl);
  41. }
  42. }

Here I have created a Model for OTP table. I need to define protected $table = 'otp'; because I have named at class in capital letters. Otherwise Laravel will look for the table o_t_p instead of otp. I have also defined a function isValidOTPTime. This function will check if OTP was generated less than 10 minutes ago. There is another function for sending OTP to the mobile number via MSG91.

Controller

Now we will add the controller class that will handle the API requests. Create a new folder Auth in laravel_authentication/app/Http/Controllers with a file named AuthController.php. Update this file with following code:

  1. <?php
  2. namespace App\Http\Controllers\Auth;
  3. // import User Model
  4. use App\Models\User;
  5. // import OTP model
  6. use App\Models\Account\OTP;
  7. // import Carbon of DateTime operations
  8. use Carbon\Carbon;
  9. use Illuminate\Http\Request;
  10. use App\Http\Controllers\Controller;
  11. use Illuminate\Support\Facades\Auth;
  12. // for storing image (user photo)
  13. use Illuminate\Support\Facades\Storage;
  14. class AuthController extends Controller {
  15. // for sending otp
  16. public function sendOTP(Request $request) {
  17. }
  18. // for login
  19. public function login(Request $request) {
  20. }
  21. // for logout
  22. public function logout(Request $request) {
  23. }
  24. // update user info (name and photo)
  25. public function updateUser(Request $request) {
  26. }
  27. // for creating new access token
  28. public function refreshToken(Request $request) {
  29. }
  30. }

Here I have imported required files and defined abstract functions. I will map these functions with API routes in the next section.

I imported Carbon\Carbon for datetime operations. Illuminate\Support\Facades\Storage; has been used for creating storage uri for user's photo. We have to complete five functions.

sendOTP()

Update sendOTP() function as:

  1. <?php // don't include this line, this is for code decoration
  2. public function sendOTP(Request $request) {
  3. // collect mobile number
  4. $mobile_no = $request->mobile_no;
  5. // validate mobile number
  6. if(!preg_match('/^[0-9]{10}+$/', $mobile_no)) {
  7. // raise Error
  8. return response()->json([
  9. 'message' => 'Invalid Mobile Number!'
  10. ], 403);
  11. }
  12. // check if account exists
  13. $user = User::firstWhere('mobile_no', $mobile_no);
  14. if($user) {
  15. $account_exists = true;
  16. } else {
  17. $account_exists = false;
  18. }
  19. // generate OTP
  20. $otp_number = random_int(1000, 9999);
  21. // store OTP in database
  22. $otp = new OTP;
  23. $otp->code = $otp_number;
  24. $otp->mobile_no = $mobile_no;
  25. $otp->save();
  26. // sent OTP to the mobile number
  27. $otp->sendToMobile();
  28. return response()->json([
  29. 'message' => 'OK',
  30. 'data' => [
  31. 'account' => $account_exists
  32. ]
  33. ]);
  34. }

login()

  1. <?php // don't include this line, this is for code decoration
  2. public function login(Request $request) {
  3. $mobile_no = $request->mobile_no;
  4. $code = $request->otp;
  5. // validate Mobile Number
  6. if(!preg_match('/^[0-9]{10}+$/', $mobile_no)) {
  7. // raise Error
  8. return response()->json([
  9. 'message' => 'Invalid Mobile Number!'
  10. ], 401);
  11. }
  12. // validate OTP
  13. if(!preg_match('/^[0-9]{4}+$/', $code)) {
  14. // raise Error
  15. return response()->json([
  16. 'message' => 'Invalid OTP!'
  17. ], 401);
  18. }
  19. // fetch OTP from table
  20. $otp = OTP::where('mobile_no', $mobile_no)
  21. ->where('code', $code)
  22. ->where('active', 1)
  23. ->first();
  24. // check if OTP is valid
  25. if(!$otp || !$otp->isValidOTPTime()) {
  26. // send Error
  27. return response()->json([
  28. 'message' => 'Invalid OTP!'
  29. ], 401);
  30. }
  31. // update valid OTP
  32. $otp->active = 0;
  33. $otp->save();
  34. // check if account exists
  35. $user = User::firstWhere('mobile_no', $mobile_no);
  36. if(!$user) {
  37. // if there is not account
  38. // create account
  39. $user = new User;
  40. $user->mobile_no = $mobile_no;
  41. $user->password = bcrypt(strval(random_int(100000, 999999)));
  42. $user->save();
  43. }
  44. // assign user to the session
  45. Auth::login($user);
  46. // generate access token
  47. $tokenResult = $user->createToken('Personal Access Token');
  48. $token = $tokenResult->token;
  49. // mark token active for next five weeks
  50. $token->expires_at = Carbon::now()->addWeeks(5);
  51. $token->save();
  52. // send token
  53. return response()->json([
  54. 'user' => $user,
  55. 'access_token' => $tokenResult->accessToken,
  56. 'token_type' => 'Bearer',
  57. 'expires_at' => Carbon::parse(
  58. $tokenResult->token->expires_at
  59. )->toDateTimeString()
  60. ]);
  61. }

I have done the following (in sequence):

  1. Collect mobile number and OTP from request
  2. Apply validations on mobile number and OTP
  3. Fetched OTP from the table
  4. Checked if OTP is valid (OTP exists and OTP was sent less than 10 minutes ago)
  5. Marked OTP used (active = false)
  6. Fetched user assigned with mobile number
  7. If there is no user in the database, a new user is created.
  8. User assigned to the session (user logged in)
  9. Created Passport Access token for 5 weeks
  10. Sent user details along with access token back to the client.

logout() and refreshToken()

For revoking access for a user, I need to invalidate his access token. I will do this in logout() function.

  1. <?php // don't include this line, this is for code decoration
  2. public function logout(Request $request) {
  3. $request->user()->token()->revoke();
  4. return response()->json(['message' => 'Successfully logged out']);
  5. }

An access token is valid for only five weeks. So client need to refresh token before the existing token expires. refreshToken() function need to be updated as follow:

  1. <?php // don't include this line, this is for code decoration
  2. public function refreshToken(Request $request) {
  3. // get user from request/session
  4. $user = $request->user();
  5. // create a new access token
  6. $tokenResult = $user->createToken('Personal Access Token');
  7. $token = $tokenResult->token;
  8. // mark validity for five weeks
  9. $token->expires_at = Carbon::now()->addWeeks(5);
  10. $token->save();
  11. // send token
  12. return response()->json([
  13. 'user' => $user,
  14. 'access_token' => $tokenResult->accessToken,
  15. 'token_type' => 'Bearer',
  16. 'expires_at' => Carbon::parse(
  17. $tokenResult->token->expires_at
  18. )->toDateTimeString()
  19. ]);
  20. }

updateUser()

After registering a new user, I am collecting user's name and photo. This need to be saved in the database through a controller. Update updateUser() as follow:

  1. <?php // don't include this line, this is for code decoration
  2. public function updateUser(Request $request) {
  3. // get user from request/session
  4. $user = $request->user();
  5. // update name
  6. $user->name = $request->name;
  7. # check is photo is sent in request
  8. if ($request->hasFile('photo')) {
  9. # check if photo is valud
  10. if ($request->file('photo')->isValid()) {
  11. # save user photo on the server
  12. $url = $request->photo->store('public/avatars');
  13. # generate photo url
  14. $user->photo = Storage::url($url);
  15. }
  16. }
  17. // save user in database
  18. $user->save();
  19. // send updated user
  20. return response()->json($request->user());
  21. }

This function will store user's image in laravel_authentication/storage/app/public/avatars. But avatars folder does not exits by default. You need to create this folder. Laravel does not render files directly from storage. I am creating symlink from laravel_authentication/public to storage folder using

  1. php artisan storage:link

You can find some alternative methods here https://stackoverflow.com/questions/30191330/laravel-5-how-to-access-image-uploaded-in-storage-within-view

Final AuthController.php

  1. <?php
  2. namespace App\Http\Controllers\Auth;
  3. use App\Models\User;
  4. use App\Models\Account\OTP;
  5. use Carbon\Carbon;
  6. use Illuminate\Http\Request;
  7. use App\Http\Controllers\Controller;
  8. use Illuminate\Support\Facades\Auth;
  9. use Illuminate\Support\Facades\Storage;
  10. class AuthController extends Controller {
  11. public function sendOTP(Request $request) {
  12. // collect mobile number
  13. $mobile_no = $request->mobile_no;
  14. // validate mobile number
  15. if(!preg_match('/^[0-9]{10}+$/', $mobile_no)) {
  16. // raise Error
  17. return response()->json([
  18. 'message' => 'Invalid Mobile Number!'
  19. ], 403);
  20. }
  21. // check if account exists
  22. $user = User::firstWhere('mobile_no', $mobile_no);
  23. if($user) {
  24. $account_exists = true;
  25. } else {
  26. $account_exists = false;
  27. }
  28. // generate OTP
  29. $otp_number = random_int(1000, 9999);
  30. // store OTP in database
  31. $otp = new OTP;
  32. $otp->code = $otp_number;
  33. $otp->mobile_no = $mobile_no;
  34. $otp->save();
  35. // sent OTP to the mobile number
  36. $otp->sendToMobile();
  37. return response()->json([
  38. 'message' => 'OK',
  39. 'data' => [
  40. 'account' => $account_exists
  41. ]
  42. ]);
  43. }
  44. //
  45. public function login(Request $request) {
  46. $mobile_no = $request->mobile_no;
  47. $code = $request->otp;
  48. // validate Mobile Number
  49. if(!preg_match('/^[0-9]{10}+$/', $mobile_no)) {
  50. // raise Error
  51. return response()->json([
  52. 'message' => 'Invalid Mobile Number!'
  53. ], 401);
  54. }
  55. // validate OTP
  56. if(!preg_match('/^[0-9]{4}+$/', $code)) {
  57. // raise Error
  58. return response()->json([
  59. 'message' => 'Invalid OTP!'
  60. ], 401);
  61. }
  62. // fetch OTP from table
  63. $otp = OTP::where('mobile_no', $mobile_no)
  64. ->where('code', $code)
  65. ->where('active', 1)
  66. ->first();
  67. // check if OTP is valid
  68. if(!$otp || !$otp->isValidOTPTime()) {
  69. // send Error
  70. return response()->json([
  71. 'message' => 'Invalid OTP!'
  72. ], 401);
  73. }
  74. // update valid OTP
  75. $otp->active = 0;
  76. $otp->save();
  77. // check if account exists
  78. $user = User::firstWhere('mobile_no', $mobile_no);
  79. if(!$user) {
  80. // if there is not account
  81. // create account
  82. $user = new User;
  83. $user->mobile_no = $mobile_no;
  84. $user->password = bcrypt(strval(random_int(100000, 999999)));
  85. $user->save();
  86. }
  87. // assign user to the session
  88. Auth::login($user);
  89. // generate access token
  90. $tokenResult = $user->createToken('Personal Access Token');
  91. $token = $tokenResult->token;
  92. // mark token active for next five weeks
  93. $token->expires_at = Carbon::now()->addWeeks(5);
  94. $token->save();
  95. // send token
  96. return response()->json([
  97. 'user' => $user,
  98. 'access_token' => $tokenResult->accessToken,
  99. 'token_type' => 'Bearer',
  100. 'expires_at' => Carbon::parse(
  101. $tokenResult->token->expires_at
  102. )->toDateTimeString()
  103. ]);
  104. }
  105. public function logout(Request $request) {
  106. $request->user()->token()->revoke();
  107. return response()->json(['message' => 'Successfully logged out']);
  108. }
  109. public function updateUser(Request $request) {
  110. // get user from request/session
  111. $user = $request->user();
  112. // update name
  113. $user->name = $request->name;
  114. # check is photo is sent in request
  115. if ($request->hasFile('photo')) {
  116. # check if photo is valud
  117. if ($request->file('photo')->isValid()) {
  118. # save user photo on the server
  119. $url = $request->photo->store('public/avatars');
  120. # generate photo url
  121. $user->photo = Storage::url($url);
  122. }
  123. }
  124. // save user in database
  125. $user->save();
  126. // send updated user
  127. return response()->json($request->user());
  128. }
  129. public function refreshToken(Request $request) {
  130. // get user from request/session
  131. $user = $request->user();
  132. // create a new access token
  133. $tokenResult = $user->createToken('Personal Access Token');
  134. $token = $tokenResult->token;
  135. // mark validity for five weeks
  136. $token->expires_at = Carbon::now()->addWeeks(5);
  137. $token->save();
  138. // send token
  139. return response()->json([
  140. 'user' => $user,
  141. 'access_token' => $tokenResult->accessToken,
  142. 'token_type' => 'Bearer',
  143. 'expires_at' => Carbon::parse(
  144. $tokenResult->token->expires_at
  145. )->toDateTimeString()
  146. ]);
  147. }
  148. }

Routing

Laravel 8 provides routes/api.php for writing APIs. In this file, define the routes as follow.

  1. <?php
  2. use Illuminate\Http\Request;
  3. use Illuminate\Support\Facades\Route;
  4. use App\Http\Controllers\Auth\AuthController;
  5. /*
  6. |--------------------------------------------------------------------------
  7. | API Routes
  8. |--------------------------------------------------------------------------
  9. |
  10. | Here is where you can register API routes for your application. These
  11. | routes are loaded by the RouteServiceProvider within a group which
  12. | is assigned the "api" middleware group. Enjoy building your API!
  13. |
  14. */
  15. Route::group([
  16. 'prefix' => 'account'
  17. ], function () {
  18. Route::post('send-otp', [AuthController::class, 'sendOTP']);
  19. Route::post('login', [AuthController::class, 'login'])->name('login');
  20. Route::group([
  21. 'middleware' => 'auth:api'
  22. ], function() {
  23. Route::post('update-user', [AuthController::class, 'updateUser']);
  24. Route::post('refresh-token', [AuthController::class, 'refreshToken']);
  25. Route::get('logout', [AuthController::class, 'logout']);
  26. });
  27. });

In the above code, I have defined all five APIs that I had mentioned in the beginning of the post. Every API in the roues/api.php has a prefix api. In line 17, I wrapped the APIs in a Route::group with prefix account. This will add account after api, i.e., now APIs will start with /api/account/. In lines 20 and 21, I have added API for sending OTP and login respectively. 

  1. Route::post('send-otp', [AuthController::class, 'sendOTP']);

In above line, Route::post means that this accepts a POST request. 'send-otp' is route path (i.e., resultant path is /api/account/send-otp). I have already defined AuthController class in project/controllers in the last section. [AuthController::class, 'sendOTP'] means that sendOTP function will be called with a request on this API is received. 

Again in line 23, I placed rest three APIs in another Route::group with a middleware. A middleware is a function that will be executed before executing the API controller function. I have added 'auth:api' middleware provided by Laravel Passport. This middleware will ensure that requesting user is authenticated (logged in), otherwise this will raise authentication error.

Configure Passport

For using Laravel Passport, I had to do some more changes. 

Update laravel_authentication/app/Providers/AuthServiceProvider.php

  1. <?php
  2. namespace App\Providers;
  3. use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
  4. use Illuminate\Support\Facades\Gate;
  5. use Laravel\Passport\Passport;
  6. class AuthServiceProvider extends ServiceProvider
  7. {
  8. /**
  9. * The policy mappings for the application.
  10. *
  11. * @var array
  12. */
  13. // add this
  14. protected $policies = [
  15. 'App\Models\Model' => 'App\Policies\ModelPolicy',
  16. ];
  17. /**
  18. * Register any authentication / authorization services.
  19. *
  20. * @return void
  21. */
  22. public function boot()
  23. {
  24. $this->registerPolicies();
  25. // add this line
  26. Passport::routes();
  27. }
  28. }

This will register Laravel Passport routes. Also update gaurds in laravel_authentication/config/auth.php as 

  1. <?php // don't include this line, this is for code decoration
  2. 'guards' => [
  3. 'web' => [
  4. 'driver' => 'session',
  5. 'provider' => 'users',
  6. ],
  7. // update these lines
  8. 'api' => [
  9. 'driver' => 'passport',
  10. 'provider' => 'users',
  11. ],
  12. ],

CORS Middleware

For using APIs on cross-domain, CORS Middleware is required. Create a new middleware file laravel_authentication/app/Http/Middleware/Cors.php and add the following code in it.

  1. <?php
  2. namespace App\Http\Middleware;
  3. use Closure;
  4. use Illuminate\Http\Request;
  5. class Cors
  6. {
  7. /**
  8. * Handle an incoming request.
  9. *
  10. * @param \Illuminate\Http\Request $request
  11. * @param \Closure $next
  12. * @return mixed
  13. */
  14. public function handle(Request $request, Closure $next)
  15. {
  16. return $next($request)
  17. ->header('Access-Control-Allow-Origin', '*')
  18. ->header('Access-Control-Allow-Methods',
  19. 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
  20. ->header('Access-Control-Allow-Headers',
  21. 'Content-Type, Authorization, X-Requested-With, X-XSRF-TOKEN');
  22. }
  23. }

Now register new middleware in app/Http/Kernal.php as:

  1. <?php // don't include this line, this is for code decoration
  2. protected $middleware = [
  3. // \App\Http\Middleware\TrustHosts::class,
  4. \App\Http\Middleware\TrustProxies::class,
  5. \Fruitcake\Cors\HandleCors::class,
  6. \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
  7. \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
  8. \App\Http\Middleware\TrimStrings::class,
  9. \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
  10. // add this line
  11. \App\Http\Middleware\Cors::class,
  12. ];

Final Step

Now test the APIs using POSTMAN (you can use alternatives like curl). All APIs are working perfectly for me. Here is my POSTMAN Collection https://www.getpostman.com/collections/83b7b35ef90cb3b3c7cd.

For starting Larvel server you need to run:

  1. php artisan serve

Server will start at localhost:8000

For more advanced demo, you can check my tutorial on Blog demo in Laravel.




Click on banner below to Learn: PHP with Laravel for beginners - Become a Master in Laravel

About Harish Kumar

Harish, a technical core team member at www.lyflink.com with five year experience in full stack web and mobile development, spends most of his time on coding, reading, analysing and curiously following businesses environments. He is a non-graduate alumni from IIT Roorkee, Computer Science and frequently writes on both technical and business topics.

Related Articles

With the expanding market of mobile apps, the developers are struggling to maintain the code bases for Native apps. M...
5 Elite and Imperative Hybrid App Frameworks
Django is a great framework in python. But all of the hosting do not provide django hosting in their shared or free h...
Cheap Django Hosting
Laravel provides blade template. Blade files are similar to php files and cover all features of php files. In additio...
Blade Template in Laravel 7

Full-Stack Web Development with React

Best Udemy Courses

Top Posts

Recent Posts

The Complete Web Developer Course - Build 25 Websites

Subscribe

Subscribe now and get weekly updates directly in your inbox!

Any Course on Udemy in $6 (INR 455)

Development Category (English)300x250

PHP with Laravel for beginners - Become a Master in Laravel