Setting Up API Call Limits In Laravel - Part 2
Tags: laravel, api, php, rest
Published: 2021-03-24 16:46:30

In this second part of my API request limiting series, I'll dive into building a custom middleware for tracking user's requests to our API endpoints and setting limits according to the user's privileges. As a reminder to the previous part, we're building a public API for our users to fetch data, these users are split into various subscription tiers and for some tiers, we need to limit their access to the API over time. Now that we're all caught up, let's get started.

# How does this work
For this to work properly, we're going to need to take into account three parts of our application - the API, the database and the middleware. This method is going to be slightly different than the one used in the previous article, as we are going to store information about the user's request to the database, rather than doing the limiting under the hood using a rate limiter. This way, we'll have more information about the request for future use, as you'll see at the end of the article.

# Setting up the database
First of all, we need to set up a database table and Laravel model for our requests. We'll achieve this with one artisan command.

```
php artisan make:model Request --migration
```

This will create the *Request* model and the *create_requests_table* migration. In my database, I have columns for request id, API token via which a given endpoint was called, the id of the user that called that endpoint, date and time of the request and the URI the user called.  The token and the user id reference the same row in the users table, so the second one is kinda redundant, I didn't want to address this problem when I was creating the API though, so we'll go with what we've got. The migration function looks like this:

```
    public function up()
    {
        Schema::create('requests', function (Blueprint $table) {
            $table->id();
            $table->string('token');
            $table->integer('user_id');
            $table->datetime('date');
            $table->string('content');
            $table->foreign('user_id')
                       ->references('id')
                       ->on('users');
            $table->foreign('token')
                       ->references('api_token')
                       ->on('users');
        });
    }
```

For the model, it is pretty straightforward. We're disabling timestamps because the rows in the table will not be updated and we've already supplied the request datetime in the *date* column. We're also making every column except *id* fillable.

```
// app/Models/Request.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Request extends Model
{

    public $timestamps = false;

    protected $fillable = [
        'token',
        'user_id',
        'date',
        'content'
    ];
}

```

# Setting up the middleware
Now that we have the database layer set up, we can go ahead and create the middleware. We're going to be referencing the *User* and *Request* models as the request is tied to the user who is calling the API via their API token. As in the previous part, we are using bearer tokens to authenticate users. To continue the subscription-based public API model we've discussed in the [last article](https://blog.dzarsky.eu/setting-up-api-call-limits-in-laravel-part-1), we'll set request limits to certain groups of users. This time, free-tier users are limited to one request to the API per day, subscribed users can make unlimited number of requests. Let's see the middleware first:

```
// app/Http/Middleware/AccessLevel.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Carbon;
use App\Models\Request as ApiRequest;

class AccessLevel
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {

        $token = $request->bearerToken();
        if (User::where('api_token', $token)->exists()) {
            $user = User::where('api_token', $token)->first();
        } else {
            return response()
                ->json(['error' => 'Bad token'], 401);
        }

        if ($user->access == 0) {
            if ($this->checkLimit($token, today())) {
                return response()
                ->json(['error' => 'Daily call limit exceeded'], 429);
            }
        }
        ApiRequest::create([
            'token' => $token,
            'user_id' => $user->id,
            'date' => Carbon::now(),
            'content' => $request->url()
        ]);
        return $next($request);

    }

    private function checkLimit($token, $date)
    {

        if (ApiRequest::where('token', $token)
            ->where('date', 'like', $date->format('Y-m-d') . '%')
            ->exists()) {
                return true;
            } else {
                return false;
        }
    }
}

```

There is much going on in this middleware, let's digest it a little bit. The request goes to a given API route and the middleware fires up. From the request, a bearer token is extracted and checked against the users table. If a user with given token exists, the middleware registers the user into a variable and continues to do other checks, however if a user with this token doesn't exist, the middleware returns a 401. When this check is complete, the middleware checks the user's *Access Level*. If the user is a free-tier user, the *checkLimit()* method fires up and checks if a user has made an API call today. The check is of course done via the *requests* table. If the user has made a request today, the middleware returns a *Too many requests* error.

If no error was triggered, the request is stored in the database for future reference (either for the middleware or for statistical reasons).

As usual, we need to register this new middleware in *app/Http/Kernel*. You should register it in both the *api* middleware group and *$routeMiddleware*. If you do not want this restriction to fire up on all of your API routes, register it only in the *$routeMiddleware* array. I have mine registered like this:

```
// app/Http/Kernel.php

<?php

...
protected $middlewareGroups = [
    'web' => [
        ...
    ],
    'api' => [
        \App\Http\Middleware\ForceJson::class,
        \App\Http\Middleware\AccessLevel::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

protected $routeMiddleware = [
    ...
    'access.level' => \App\Http\Middleware\AccessLevel::class,
];
```

If you choose not to register the middleware in the *api* middleware group, you will also need to register it in your *routes/api.php* file. Let's test the middleware to make sure it works...

![Test for a free-tier user](https://cdn.hashnode.com/res/hashnode/image/upload/v1616408438477/uMavneNr7.png)

As you can see in the picture above, we've only managed to get one request through before getting the *Too many requests* error when we supplied a non-subscribed user's token, **thus fulfilling our goal**.

# Using the collected data
As I've used this technique to limit API requests for users on my own site - [OnlyResultz](https://www.onlyresultz.com), I wanted to display relevant API usage metrics to my users via the user's dashboard. For that use case, I displayed only daily, monthly and lifetime requests, but more ways to use the collected data exist, for your users and your application's administrators alike. An example of such functionality in Blade can be found in this [Gist](https://gist.github.com/Loupeznik/4f232445f0cc520b784af027a14b9f7d).

And the end result looks like this:

![Example metrics](https://cdn.hashnode.com/res/hashnode/image/upload/v1616424889615/G3t1vjYHF.png)

In the blade file above, we are supplying the *$requests* variable, which uses the *Request* model to fetch relevant requests for the user. The individual metrics are processed inside the blade file and served to the user in a readable form.

# Wrapping up
This has been it for the *Setting Up API Call Limits In Laravel* series. Check out the previous article in the series if you haven't yet. In the other article, I explain how to use Laravel's built-in Rate Limiter to achieve similar result to that which we've seen in this article.

Until next time.

Cover image by [Mohamed Hassan](https://pixabay.com/users/mohamed_hassan-5229782/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3967926) from [Pixabay](https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=3967926)