Add Missing Migrations to Laravel’s Database Without executing

Learn how to add missing Laravel migration entries to the database without re-running them. Perfect for restoring synced environments or imported databases.

Sometimes, when working with Laravel projects, especially when migrating databases manually or syncing environments, the migrations table might miss entries — even though the migration files exist and were executed. This causes Laravel to attempt to re-run migrations or show them as pending. And even sometime, you added new migration and try to migrate them, but migration gives error that previous migrations are pending.

This article will guide you through creating an Artisan command that adds missing migration entries to the database without running them, keeping Laravel in sync with the actual DB schema.

Step 1: Create the Command

To create an artisan command, run the following command in your terminal:

php artisan make:command SyncMigrations

It creates a file at app/Console/Commands/SyncMigrations.php.

Step 2: Add the Logic

Now, open this file and paste the following code inside it:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;

class SyncMigrations extends Command
{
    protected $signature = 'sync:migrations';
    protected $description = 'Add missing migrations to the migrations table without running them';

    public function handle()
    {
        $migrationPath = database_path('migrations');
        $files = File::files($migrationPath);

        $fileMigrations = collect($files)->map(function ($file) {
            return pathinfo($file->getFilename(), PATHINFO_FILENAME);
        });

        $existingMigrations = DB::table('migrations')->pluck('migration');

        $missingMigrations = $fileMigrations->diff($existingMigrations);

        if ($missingMigrations->isEmpty()) {
            $this->info('✅ All migrations are already recorded.');
            return 0;
        }

        $lastBatch = DB::table('migrations')->max('batch') ?? 0;
        $nextBatch = $lastBatch + 1;

        foreach ($missingMigrations as $migration) {
            DB::table('migrations')->insert([
                'migration' => $migration,
                'batch' => $nextBatch,
            ]);
            $this->line("✔️  Added migration: <info>$migration</info>");
        }

        $this->info("🎉 Added " . $missingMigrations->count() . " missing migration(s) to batch $nextBatch.");
        return 0;
    }
}

Explanation:

  • Get all filenames list from the migrations folder.
  • Get all migrations list from the migrations table.
  • Check a difference between two lists and return 0 if no difference found.
  • If there is a difference, get a last migration batch and create new batch by adding 1 to this batch number.
  • Add all migrations available in the difference with new batch number.

Step 3: Run the Command

If you are running Laravel < 11, register the command file in app/Console/Kernel.php:

protected $commands = [
    \App\Console\Commands\SyncMigrations::class,
];

In Laravel 11+, there’s no need to register commands manually.

Now, run the following command in your terminal:

php artisan sync:migrations

You’ll see output like this:

✔️  Added migration: 2024_12_31_235959_create_users_table
✔️  Added migration: 2025_01_01_000000_create_orders_table
🎉 Added 2 missing migration(s) to batch 4.

Caution

This command does not execute the migrations. It assumes the database schema already reflects them. Use this only when you’re sure the migrations were already applied, e.g. from another environment or a database import.

Conclusion

This approach is a safe and Laravel-friendly way to fix out-of-sync migrations. It’s perfect for developers working across multiple environments or restoring production databases.

Want to take it further? Add a prompt or backup feature to this command. Let me know in the comments!

Implementing JWT Authentication in CodeIgniter 3

Learn how to implement secure JWT authentication in CodeIgniter 3. Step-by-step guide for token generation, validation, and integration.

Securing your mobile API is critical in modern applications. In this guide, we’ll walk through how to implement JWT (JSON Web Token) based authentication in CodeIgniter 3, including access token and refresh token support for long-lived sessions.

Overview of JWT Auth Flow

Here’s the standard flow:

  1. User logs in → server returns an access token and a refresh token.
  2. Mobile app uses the access token in the Authorization header for every request.
  3. When access token expires, the app sends the refresh token to get a new access token.

Prerequisites

  • CodeIgniter 3 installed
  • firebase/php-jwt JWT library via Composer
  • users table for authentication and user_tokens table for refresh tokens

Step 1: Install JWT Library

Use composer to install JWT library as follows:

composer require firebase/php-jwt

Step 2: Create JWT Helper Class

Create a JWT helper class file at application/libraries/Authorization_Token.php and add the following code to it:

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class Authorization_Token {
    private $CI;
    private $token_key;

    public function __construct() {
        $this->CI =& get_instance();
        $this->token_key = 'YOUR_SECRET_KEY';
    }

    public function generateToken($user_data) {
        $issuedAt = time();
        $expirationTime = $issuedAt + 3600; // 1 hour
        $payload = [
            'iat' => $issuedAt,
            'exp' => $expirationTime,
            'data' => $user_data
        ];
        return JWT::encode($payload, $this->token_key, 'HS256');
    }

    public function validateToken() {
        $headers = apache_request_headers();
        if (!isset($headers['Authorization'])) return false;
        
        $token = str_replace('Bearer ', '', $headers['Authorization']);
        try {
            $decoded = JWT::decode($token, new Key($this->token_key, 'HS256'));
            return (array) $decoded->data;
        } catch (Exception $e) {
            return false;
        }
    }
}

Step 3: Create Login API

Create a user_tokens table for storing refresh tokens.

CREATE TABLE user_tokens (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    refresh_token VARCHAR(255) NOT NULL,
    expires_at DATETIME NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Create a login API file anywhere inside app/controllers folder and add the following content inside it.

class Auth extends CI_Controller {
    public function login_post()
    {
        $email = $this->post('email');
        $password = $this->post('password');

        $user = $this->db->get_where('users', ['email' => $email])->row();

        if (!$user || !password_verify($password, $user->password)) {
            return $this->response(['status' => false, 'message' => 'Invalid credentials'], 401);
        }

        $this->load->library('Authorization_Token', null, 'authToken');
        $access_token = $this->authToken->generateToken(['id' => $user->id, 'email' => $user->email]);

        $refresh_token = bin2hex(random_bytes(64));
        $this->db->insert('user_tokens', [
            'user_id' => $user->id,
            'refresh_token' => $refresh_token,
            'expires_at' => date('Y-m-d H:i:s', strtotime('+30 days'))
        ]);

        return $this->response([
            'status' => true,
            'access_token' => $access_token,
            'refresh_token' => $refresh_token,
        ], 200);
    }
}

Step 4: Protect API Routes

Create a base controller file BaseApi_Controller.php inside app/controllers folder. Add the following code to base controller file.

class BaseApi_Controller extends REST_Controller
{
    public $user_data;

    public function __construct()
    {
        parent::__construct();
        $this->load->library('Authorization_Token', null, 'authToken');
        $user_data = $this->authToken->validateToken();

        if (!$user_data) {
            $this->response([
                'status' => false,
                'message' => 'Access token expired',
                'token_expired' => true
            ], 401);
            exit;
        }

        $this->user_data = $user_data;
    }
}

This file handles token validations for all requests. But, it will not automatically intercept all requests. So, all your secure API files need to extend this BaseApi_Controller.

class Orders extends Authenticated_Controller {
    public function list_get() {
        $user_id = $this->user_data['id'];
        $orders = $this->db->get_where('orders', ['user_id' => $user_id])->result();

        $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode($orders));
    }
}

Step 5: Token Refresh

Create new api file AuthController.php for refresh token and add the following code in it.

class Auth extends CI_Controller {
    public function refresh_token_post()
    {
        $refresh_token = $this->post('refresh_token');

        $token_data = $this->db->get_where('user_tokens', [
            'refresh_token' => $refresh_token
        ])->row();

        if (!$token_data || strtotime($token_data->expires_at) < time()) {
            return $this->response([
                'status' => false,
                'message' => 'Invalid or expired refresh token'
            ], REST_Controller::HTTP_UNAUTHORIZED);
        }

        // Generate new access token
        $this->load->library('Authorization_Token', null, 'authToken');
        $access_token = $this->authToken->generateToken([
            'id' => $token_data->user_id,
            'email' => 'user@email.com' // Fetch if needed
        ]);

        return $this->response([
            'status' => true,
            'access_token' => $access_token,
            'expires_in' => 900
        ], REST_Controller::HTTP_OK);
    }
}

Summary

  • JWT access tokens: short-lived (e.g., 15 minutes)
  • Refresh tokens: long-lived (e.g., 30 days), stored securely
  • On access token expiry: client uses refresh token to get a new one
  • REST_Controller is used to simplify JSON responses in CodeIgniter 3

Final Thoughts

Implementing access and refresh tokens properly ensures secure and scalable mobile API sessions. Using CodeIgniter 3 with JWT and refresh tokens gives you full control over session lifecycle, security, and logout behavior.

Install the Laravel Filament Panel Builder

Learn to install and set up Laravel Filament, a tool for creating customizable admin panels and CRUD applications. This guide covers requirements, installation steps, and user creation. Follow this concise tutorial to get started with Filament in your Laravel project quickly and efficiently.

Laravel Filament is a powerful tool designed to create Admin panels and manage content in Laravel applications. It provides a highly customizable and developer-friendly interface for creating CRUD applications, dashboards, and various business-related applications. Filament is known for its flexibility and ease of use, allowing developers to scaffold forms, tables and pages quickly without writing a lot of boilerplate code.

This article describes the installation process filament panel over laravel with most of the possible configations and steps.

Requirements

Install and configure the following components, before running any filament command.

  • PHP v8.1+
  • Laravel v10.0+
  • Livewire v3.0+ (Filament composer command will install this package automatically. So, no need to install this package separately.)

Install Laravel Filament Panel

To install the filament panel over laravel, run the following command at the root of the project folder,

composer require filament/filament:"^3.2" -W

This command will install the base package of filament. This will also install livewire package, which is in the requirements.

php artisan filament:install --panels

This command will install the filament panel after some information required to install the panels. It will ask the following questions,

What is the ID?

On the basis of this ID, it will create the panel provide for the filament panel and also register this panel provider.

For Example:
If ID is admin, it will create the panel provide to he following location, app/Providers/Filament/AdminPanelProvider.php

If you encounter an error when accessing your panel, ensure that the service provider is registered in bootstrap/providers.php (for Laravel 11 and above) or config/app.php (for Laravel 10 and below). If it isn’t, you’ll need to add it manually.

Create a User

Next step is creating a user to access this panel. But, before running the create user command, check the following laravel configuration and update the configuration as per the requirements,

  • Add Database credentials to .env file.
  • Run the following command to run the migration. It will create users table into the database.
    php artisan migrate

Run the following command to create a user after checking above requirements,

php artisan make:filament-user

It will ask some basic questions like name, email, password, etc. for creating a user.

After creating a user, run php artisan serve, open http://127.0.0.1:8000/admin in your web browser, sign in using the created user credentials, and start building your app!

Laravel Queues: An Asynchronous Processing

This article aims to guide you through the process of executing jobs asynchronously using queues and workers in Laravel.

When it comes to web applications and softwares, speed is crucial for a satisfactory user experience. However, certain tasks require more time to complete, such as sending automated emails, importing large data from CSV or excel files or generating complex reports from extensive data sets. These kinds of tasks cannot be performed synchronously. The well-designed web applications utilize such asynchronous processing in the background, which can be performed using a capability provided by Laravel Queues.

Prerequisites:

This article aims to guide you through the process of executing jobs asynchronously using queues and workers in Laravel. Prior to diving in, please ensure that you have PHP, Composer and MySQL installed on your system, and that you are familiar with bootstrapping and initiating a new Laravel project.

The Concept:

Before delving into the code, I would like to describe the concept of asynchronous processing with a real-life analogy. Imagine yourself as the owner of a local grocery shop that accepts orders over the phone and delivers the requested items to customers’ doorsteps.

Now, assume that you receive a phone call from a customer and take a new order. You write this order on the piece of paper. You then proceed to the storeroom, locate the individual products, pack them into a box, and arrange for delivery. In this scenario, you cannot accept any new orders until you have dispatched the current one. This is called synchronous approach. It blocks your ability to handle other tasks until you complete the currently running process at hand.

However, instead of sticking to a synchronous model, you have the option to hire an additional worker for the storeroom. By doing so, you can accept a new order, jot it down on a piece of paper (referred to as a job), and place it in a queue. One of the storeroom workers will pick a job from the queue, assemble the order, and arrange for delivery. Afterwards, the worker will return to the queue, select the next job, and commence its processing. This is called an asynchronous approach. It  allows for a more efficient workflow. While employing extra workers may require additional resources, it enables you to handle more orders without impeding your overall productivity. This, in turn, leads to improved performance and heightened customer satisfaction.

The Basic Implementation:

Now, let’s see how to implement this asynchronous approach of a job, a worker, and a queue in Laravel. To start, create a new Laravel project somewhere on your computer. Open the project in your favourite IDE and update the code for the routes/web.php file as follows:

Route::get('/', function () {

    dispatch(function() {
        sleep(5);
        logger('job done!');
    });

    return view('welcome');
});

The dispatch() helper function in Laravel sends a new job to a queue. Here, a job is regular PHP code in the form of a closure or class. You can also use job classes instead of closures in above code.

To simulate a long-running task, I have added an explicit delay of five seconds using the sleep function in the job closure. Then, I used the logger() helper function to print the line “job done!” in the project’s laravel.log file located at storage/logs/laravel.log.

Now, start the project by running the following command:

php artisan serve

And open http://127.0.0.1:8000 in your browser. You will see the browser loading for five seconds, and then the welcome page will show up. If you check the log file at location storage/logs/laravel.log, you will see the following log entry:

[2021-08-23 14:37:50] local.DEBUG: job done!

This means that the job ran successfully after the five-second delay, but still it is not running asynchronously. It is still blocking the I/O operations as the browser was in a loading state for five seconds. It is because of the QUEUE_CONNECTION variable in the project’s .env file. This variable indicates the connection to be used in the queue service in Laravel. By default, it has been set to sync, which means that Laravel will process all the jobs synchronously.

Asynchronous Approach:

For asynchronous job-processing, you will have to use a different connection. Laravel has provided a pre-configured list of connections inside the config/queue.php file as follows:

'connections' => [

    'sync' => [
        'driver' => 'sync',
    ],

    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 90,
        'after_commit' => false,
    ],

    'beanstalkd' => [
        'driver' => 'beanstalkd',
        'host' => 'localhost',
        'queue' => 'default',
        'retry_after' => 90,
        'block_for' => 0,
        'after_commit' => false,
    ],

    'sqs' => [
        'driver' => 'sqs',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
        'queue' => env('SQS_QUEUE', 'default'),
        'suffix' => env('SQS_SUFFIX'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
        'after_commit' => false,
    ],

    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
        'retry_after' => 90,
        'block_for' => null,
        'after_commit' => false,
    ],
],

You can use any connection except sync,which is used for synchronous job-processing. For this example, we will use the database connection. To update the connection, open the project’s .env file and change the value of QUEUE_CONNECTION to database and save the file.

But, to work with database connection, we have to create a table to maintain our asynchronous jobs. To create the migration for this table, run the following command:

php artisan queue:table

It will generate the migration script for creating the jobs table. Open the newly generated migration script, it will have the following content:

<?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('jobs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('queue')->index();
            $table->longText('payload');
            $table->unsignedTinyInteger('attempts');
            $table->unsignedInteger('reserved_at')->nullable();
            $table->unsignedInteger('available_at');
            $table->unsignedInteger('created_at');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('jobs');
    }
};

Now, run the migration command as follows to create the jobs table in the database:

php artisan migrate

This table will hold all the queued jobs until a worker processes them. Before starting the laravel, clear the project’s log file located at storage/logs/laravel.log and run the following command:

php artisan serve

And open http://127.0.0.1:8000 in your browser. This time, the page will render almost immediately. After waiting for five seconds, if you check the storage/logs/laravel.log file, you’ll find it empty. Because, the above setup will only add the job into the jobs table. But, execution of this job is still pending.

If you look at the jobs table in your database, you’ll see that the framework has pushed a new job to the queue. To execute this job, we have to run the Laravel queue worker process.

Laravel Queue Worker:

A queue worker is a regular process that runs in the background and polls the queue for unprocessed jobs. To start a new worker, execute the following command inside your project directory:

php artisan queue:work

The worker will start and begin processing the unprocessed job immediately. Now, check the storage/logs/laravel.log file. It will display the following logs: 

[2021-08-23 15:30:34][1] Processing: Closure (web.php:18)
[2021-08-23 15:30:39][1] Processed:  Closure (web.php:18)

Note: To process jobs automatically in the future, you’ll have to keep the worker process running.

And that concludes this article. I have covered the basic concepts of how synchronous and asynchronous approach are working and how you can use Laravel Queues to run asynchronous jobs. There are numerous other aspects of queue management that you should familiarize yourself with, such as job uniqueness, handling race conditions, and implementing throttling. It is advisable to begin incorporating queues into your applications and learn through hands-on experience. Additionally, make sure to thoroughly explore the official documentation on Laravel Queues. If you have any questions or concerns about any of the concepts presented in this article, please feel free to reach out to me on LinkedIn, or GitHub. Until the next article, stay safe and continue your learning journey.

Laravel Middleware Explained: What It Is & How to Create One

Learn what middleware is in Laravel, why it’s essential, and how to create custom middleware for handling requests efficiently. Step-by-step guide included.

Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application. It’s best to envision middleware as a series of “layers” for HTTP requests that must pass through before they hit your application. Each layer can examine the request and even reject it entirely.

For example, Laravel includes a middleware that verifies the authenticity of the user of your application. If the user is not authenticated, the middleware will redirect the user to your application’s login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.

To perform different tasks, we can develop many middlewares besides authentication. For example, a logging middleware might log all incoming requests to your application. 

Laravel framework has included many middlewares, including middleware for authentication and CSRF protection. All of these middlewares are located in the app/Http/Middleware directory.

Create a custom middleware

To create a middleware, we can use the following command,

php artisan make:middleware <middleware-name>

For example, if we want to create a middleware for checking transactions, we can run the following command,

php artisan make:middleware CheckTransaction

 After successful execution of the command, a middleware class will be created under the app/Http/Middleware directory.

In this class, we can define methods to check transactions. If the transaction is not completed, we can redirect the user back to the failed transaction page. However, on the successful transactions, we can allow users to proceed to the next page.

<?php
 
namespace App\Http\Middleware;
 
use Closure;
 
class CheckTransaction
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->input('status') !== 'completed') {
            return redirect('transaction-failed');
        }
 
        return $next($request);
    }
}

As you can see, if the transaction status does not set to “completed”, the middleware will return an HTTP redirect to the client; otherwise, the request will be passed further into the application.

To pass the request deeper into the application (allowing the middleware to “pass”), you should call the $next callback with the $request.