# Laravel middleware: WithTransaction

Hey guys,

I've used the Transaction Wrapper middleware for my action APIs for a while. It comes in pretty handy.

For the action APIs where multiple DB write operations can happen, best to wrap them in a transaction for the data consistency.

## The good'ol way

Likely in the Laravel Controller, we usually use the 2 options, let's check them out below:

### The Options

#### Start - Try Commit - Catch Rollback

Legends know this haha 😎

```php
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;

class UserController
{
    public function update(UpdateUserRequest $request, User $user): JsonResponse
    {
        DB::beginTransaction();

        try {
            $user->update($request->getUpdatableField());
            $user->updateProfilePicture($request->getProfilePic());
            
            DB::commit();

            return new JsonResponse(['outcome' => 'SUCCESS']);
        } catch (Throwable) {
            DB::rollBack();

            return new JsonResponse(['outcome' => 'FAILED_TO_UPDATE']);
        }
    }
}
```

Within the new Laravel version, you'd be able to use the `rescue` HoF:

```php
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;

class UserController
{
    public function update(UpdateUserRequest $request, User $user): JsonResponse
    {
        DB::beginTransaction();

        return rescue(
            function () use ($request, $user) {
                $user->update($request->getUpdatableField());
                $user->updateProfilePicture($request->getProfilePic());
            
                DB::commit();

                return new JsonResponse(['outcome' => 'SUCCESS']);
            },
            function () {
                DB::rollBack();

                return new JsonResponse(['outcome' => 'FAILED_TO_UPDATE']);
            }
        );
    }
}
```

#### Use DB::transaction HoF

Instead of using `rescue` and also do the `beginTransaction` , you can do something better & shorter:

```php
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;

class UserController
{
    public function update(UpdateUserRequest $request, User $user): JsonResponse
    {
        return DB::transaction(function () use ($request, $user) {
            $user->update($request->getUpdatableField());
            $user->updateProfilePicture($request->getProfilePic());
            
            return new JsonResponse(['outcome' => 'SUCCESS']);
        });
    }
}
```

### The problem with the above options

* Isn't reusable.
    
* Your code is always inside a scope level, doesn't look so cool.
    

So why wouldn't we create something that is reusable, and get rid of a scoped level?

That's the reason for this article hehe 😎.

## WithTransaction middleware

The goal of this middleware is to wrap a whole Controller's action inside a middleware, and rollback when there is an exception. Also reusable, we just simply register the middleware in the `routes`

```php
Route::post('/users/{user}', [UserController::class, 'update'])
    ->middleware(['withTransaction']);
```

### The simple Middleware

```php
<?php

namespace App\Http\Middleware;

use App\Http\JsonResponseFactory;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class WithTransaction
{
    public function handle(Request $request, Closure $next): Response
    {
        return DB::transaction(function () use ($request, $next) {
            $result = $next($request);

            if (isset($result->exception)) {
                throw $result->exception;
            }

            return $result;
        });
    }
}
```

Laravel has its Error Response layer, after triggering the `$next`, we won't get any exceptions.

If there is an exception, the result will have the `exception` property. That's why we'll need to check & throw to commit or roll back the transaction.

This would do the job, but I don't like this way because we'll expose the internal error to the client side, therefore I came up with another better solution.

### The improved version

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class WithTransaction
{
    public function handle(Request $request, Closure $next): Response
    {
        return rescue(
            fn () => DB::transaction(function () use ($request, $next) {
                $result = $next($request);

                if (isset($result->exception)) {
                    throw $result->exception;
                }

                return $result;
            }),
            function (Throwable $e) use ($request) {
                // report($e); // you can also do this
                Log::error('Rolled back transaction on error', [
                    'error' => [
                        'msg' => $e->getMessage(),
                        'traces' => $e->getTrace(),
                    ],
                    'url' => $request->fullUrl(),
                ]);

                return new JsonResponse([
                    'outcome' => 'ACTION_FAILED',
                    ...(config('app.debug')
                        ? ['msg' => $e->getMessage()]
                        : []),
                ], 400);
            },
        );
    }
}
```

With this improved way, I'll have:

* Internal error log - would be really useful for staging & production environments.
    
* Standardized error response
    

### Notes

* If your endpoint is purely read, *DO NOT* use it, for better performance.
    
* If your action has only a single write/update operation, you don't have to use it either.
    

## Conclusion

I hope that this Middleware can bring some joy to your Controller 🥰 and get rid of the tedious code.

Happy friyay!
