API Gateway using PAGI::FastAPI

3 min read
← Back to Blogs
API Gateway using PAGI::FastAPI

DISCLAIMER: Image is generated using ChatGPT.



Just released the PAGI::FastAPI v1.2.0. This release brings it even more closer to Python's FastAPI implementation. There are still pending things, which I have documented in the TODO document.

Right now, my brain is actively thinking about PAGI::FastAPI all the time. I keep trying to explore what is possible to build useful utility with it.

In the previous post, I created websocket with it.

In this post, I will present a very simple API Gateway demo. Actually this demo app is shipped with the release of v1.2.0.

                                             +-------------------+
                                             | User Microservice |
                                             +-------------------+
                                            /
 +----------+        +-------------+       /
 |  Client  | -----> | API Gateway | ----->
 +----------+        +-------------+       \
                                            \
                                             +--------------------+
                                             | Order Microservice |
                                             +--------------------+

User Microservice

This accepts route param, user id and returns the static user details (just for demo purpose).

Source: https://github.com/manwar/PAGI-FastAPI/blob/master/eg/user_microservice.pl

#!/usr/bin/env perl

use v5.36;
use PAGI::FastAPI;
use Future::AsyncAwait;

my $app = PAGI::FastAPI->new(
    title   => "User Microservice",
    version => "1.0.0"
);

$app->get('/users/{id}',
    handler => async sub ($c) {
        my $user_id = $c->param('id') // 1;
        return {
            data => {
                id    => 0 + $user_id,
                name  => "Alice Developer",
                role  => "Software Engineer",
                email => 'alice@example.com'
            }
        };
    });

$app->to_app;

Order Microservice

This accepts route param, user id and returns the static user order details (just for demo purpose).

Source: https://github.com/manwar/PAGI-FastAPI/blob/master/eg/order_microservice.pl

#!/usr/bin/env perl

use v5.36;
use PAGI::FastAPI;
use Future::AsyncAwait;

my $app = PAGI::FastAPI->new(
    title   => "Order Microservice",
    version => "1.0.0"
);

$app->get('/orders/user/{id}',
    handler => async sub ($c) {
        my $user_id = $c->param('id') // 1;
        return {
            user_id => 0 + $user_id,
            orders  => [
                { id => 101, item => "Mechanical Keyboard", price => 120 },
                { id => 102, item => "4K Monitor",          price => 350 }
            ]
        };
    });

$app->to_app;

API Gateway

Finally we build the public facing API Gateway that link the two microservices.

I must point out, we are using, two very important features of PAGI::FastAPI i.e. PAGI::FastAPI::Security::HTTPBearer and PAGI::FastAPI::Depends.

I am not fully satisfied with it as I don’t like manually creating HTTP client. I will see if I can improve it later.

Source: https://github.com/manwar/PAGI-FastAPI/blob/master/eg/api_gateway.pl

use v5.36;
use JSON::PP;
use Future::HTTP;
use PAGI::FastAPI;
use Future::AsyncAwait;
use PAGI::FastAPI::Depends qw(Depends);
use PAGI::FastAPI::Security::HTTPBearer;

my $app = PAGI::FastAPI->new(
    title   => "API Gateway Dashboard",
    version => "1.0.0"
);

my $html_content = do { local $/; <DATA> };

# 1. Bearer Token Extractor
my $bearer = PAGI::FastAPI::Security::HTTPBearer->new;

# 2. Token Validator Dependency
my $auth_check = async sub ($c) {
    my $token = $c->stash->{authToken} // '';
    unless ($token eq 'secret-token-123') {
        $c->status(401);
        die { message => 'Unauthorised Access' };
    }
    return 1;
};

# 3. Async HTTP Client Dependency
my $get_http = async sub ($c) {
    return Future::HTTP->new;
};

# 4. Service Endpoints Config Dependency
my $get_config = async sub ($c) {
    return {
        user_service_url  => 'http://127.0.0.1:3001',
        order_service_url => 'http://127.0.0.1:3002',
    };
};

$app->get('/', handler => async sub ($c) {
    return $c->html($html_content);
});

$app->get('/api/v1/dashboard/{id}',
    dependencies => [
        $bearer->depends(key => 'authToken'),
        Depends($auth_check),
        Depends($get_http,   key => 'http_client'),
        Depends($get_config, key => 'config'),
    ],
    handler => async sub ($c) {
        # Retrieve injected dependencies from stash
        my $http    = $c->stash->{http_client};
        my $config  = $c->stash->{config};
        my $user_id = $c->param('id') // 1;

        # Concurrent, non-blocking HTTP requests
        my $user_f  = $http->http_get("$config->{user_service_url}/users/$user_id");
        my $order_f = $http->http_get("$config->{order_service_url}/orders/user/$user_id");

        # Await both futures concurrently
        await Future->wait_all($user_f, $order_f);

        my $user_body  = eval { ($user_f->get)[0]        } // '{}';
        my $order_body = eval { ($order_f->get)[0]       } // '{}';

        my $user_data  = eval { decode_json($user_body)  } // {};
        my $order_data = eval { decode_json($order_body) } // {};

        return {
            user   => $user_data->{data}    // undef,
            orders => $order_data->{orders} // [],
        };
    }
);

$app->to_app;

Demo

Let’s start the API Gateway first:

$ pagi-server api_gateway --port 3000

Then the two microservices in separate terminals:

$ pagi-server user_microservice --port 3001
$ pagi-server order_microservice --port 3002

Now visit the API Gateway dashboard: http://localhost:3000

The end result looks like this:



Happy Hacking !!!