Thursday 18 January 2024

browse, search, and purchase books using perl DBIx::Class , Mysql and GraphQL


A web-based book store that allows users to browse, search, and purchase books. The system uses DBIx::Class as an ORM layer to interact with a MySQL database, a RESTful API for communicating between the frontend and backend, and GraphQL for querying the data.

Modules Used:

  • DBIx::Class for ORM
  • LWP::UserAgent for making HTTP requests
  • JSON::XS for parsing JSON data
  • GraphQL::Query for generating GraphQL queries
  • GraphQL::Response for parsing GraphQL responses


#!/usr/bin/env perl

use strict;
use warnings;

# Set up database connection
my $dsn = 'mysql://user:password@host:port/database';
my $dbh = DBI->connect($dsn, {AutoCommit => 0});
my $schema = DBIx::Class::Schema->new($dbh);

# Define classes
__PACKAGE__->mk_class('Book', {
    -table   => 'books',
    -columns => [
        id => {
            data_type => integer,
            primary_key => 1,
        },
        title => {
            data_type => string,
        },
        author => {
            data_type => string,
        },
        published_date => {
            data_type => datetime,
        },
        price => {
            data_type => decimal,
        },
    ],
});

__PACKAGE__->mk_class('Author', {
    -table   => 'authors',
    -columns => [
        id => {
            data_type => integer,
            primary_key => 1,
        },
        name => {
            data_type => string,
        },
    ],
});

# Create RESTful API
my $api = REST::API->new();

$api->get('/books');
$api->get('/books/:id');
$api->post('/books');
$api->put('/books/:id');
$api->delete('/books/:id');

# Define GraphQL schema
my $query = GraphQL::Query->new(
    type_definitions => q{
        type Query {
            books: [Book]
            book(id: Int): Book
        }

        type Book {
            id: Int!
            title: String!
            author: Author
            publishedDate: DateTime
            price: Decimal
        }

        type Author {
            id: Int!
            name: String!
        }
    },
);

# Handle GraphQL queries
sub handle_graphql {
    my ($self, $query, $variables) = @_;

    # Get the desired data from the database
    my $books = $schema->resultset('Book')->all();

    # Return the data in the format expected by GraphQL
    return {
        data => {
            books => $books,
        },
    };
}

# Start the web server
my $server = HTTP::Server->new(
    address => 'localhost',
    port => 8080,
);

$server->add_handler(GET => '/api/v1/books', sub {
    my ($request, $response) = @_;
    my $books = $schema->resultset('Book')->all();
    $response->content(JSON::XS->encode({ books => $books }));
});

$server->add_handler(GET => '/api/v1/books/:id', sub {
    my ($request, $response) = @_;
    my $book_id = $request->param('id');
    my $book = $schema->resultset('Book')->find({ id => $book_id });
    $response->content(JSON::XS->encode({ book => $book }));
});

$server->add_handler(POST => '/api/v1/books', sub {
    my ($request, $response) = @_;
    my $data = JSON::XS->decode($request->content());
    my $book = $schema->resultset('Book')->create($data);
    $response->content(JSON::XS->encode({ book => $book }));
});
$server->add_handler(PUT => '/api/v1/books/:id', sub {
my ($request, $response) = @_;
my $book_id = $request->param('id');
my $data = JSON::XS->decode($request->content());
my $book = $schema->resultset('Book')->find({ id => $book_id });
$book->update($data);
$response->content(JSON::XS->encode({ book => $book }));
});

$server->add_handler(DELETE => '/api/v1/books/:id', sub {
my ($request, $response) = @_;
my $book_id = $request->param('id');
my $book = $schema->resultset('Book')->find({ id => $book_id });
$book->delete();
$response->content(JSON::XS->encode({ message => 'Book deleted successfully' }));
});


Start the web server
$server->start();

This code sets up a web server that listens on port 8080 and responds to GET, POST, PUT, and DELETE requests at the `/api/v1/books` endpoint. It uses the `DBIx::Class` module to interact with the database, and the `JSON::XS` module to encode and decode JSON data.

The `handle_graphql` function is called whenever a GraphQL query is received, and it retrieves the data from the database using the `DBIx::Class` module. It then returns the data in the format expected by GraphQL.

The `handle_rest` function is called whenever a non-GraphQL request is received, and it handles the request using the `REST::Client` module. It retrieves the data from the database using the `DBIx::Class` module, and then formats the response using the `JSON::XS` module.

You can test the API by sending requests to `http://localhost:8080/api/v1/books`. For example, you can send a GET request to retrieve all the books:

curl http://localhost:8080/api/v1/books

This should return a JSON array of books. You can also send a POST request to create a new book:

curl -X POST -H "Content-Type: application/json" -d '{"title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "published_date": "1925-04-10", "price": 19.99}' http://localhost:8080/api/v1/books

This should create a new book with the specified title, author, published date, and price. You can also send a PUT request to update an existing book:

curl -X PUT -H "Content-Type: application/json" -d '{"title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "published_date": "1925-04-10", "price": 19.99}' http://localhost:8080/api/v1/books/1

This should update the book with ID 1 with the specified title, author, published date, and price. Finally, you can send a DELETE request to delete a book:

curl -X DELETE http://localhost:8080/api/v1/books/1

This should delete the book with ID 1.

we can start with the GET /books/{id} endpoint, which retrieves a single book by its ID.

Here's an example implementation:

### GET /books/{id}
$app->get('/books/:id', async {
    my $id = param('id');
    my $book = $schema->resultset('Book')->find({ id => $id });
    return $book;
});

This endpoint uses the param() method to extract the id parameter from the URL, and then uses the find() method of the ResultSet object to retrieve the book with the matching ID. The async keyword is used to indicate that the endpoint is asynchronous, meaning that it returns a promise that resolves to the book object.

Next, we can implement the POST /books/ endpoint, which creates a new book.

### POST /books/
$app->post('/books/', async {
    my $data = json->decode($req->body);
    my $book = $schema->resultset('Book')->create($data);
    return $book;
});

This endpoint uses the json->decode() method to parse the JSON data sent in the request body, and then uses the create() method of the ResultSet object to create a new book with the provided data. The async keyword is used to indicate that the endpoint is asynchronous, meaning that it returns a promise that resolves to the newly created book object.

Finally, we can implement the PUT /books/{id} endpoint, which updates a single book.

### PUT /books/{id}
$app->put('/books/:id', async {
    my $id = param('id');
    my $data = json->decode($req->body);
    my $book = $schema->resultset('Book')->find({ id => $id });
    $book->update($data);
    return $book;
});

This endpoint uses the param() method to extract the id parameter from the URL, and then uses the find() method of the ResultSet object to retrieve the book with the matching ID. It then uses the update() method of the Book object to update the book with the provided data. The async keyword is used to indicate that the endpoint is asynchronous, meaning that it returns a promise that resolves to the updated book object.

With these endpoints implemented, we now have a fully functional RESTful API for managing books. We can use tools like curl or a web browser to test the endpoints and verify that they are working correctly.

For example, we can use the following command to create a new book:

curl -X POST -H "Content-Type: application/json" -d '{"title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "published_date": "1925-04-10", "price": 19.99}' http://localhost:8080/books/

This should create a new book with the specified title, author, published date, and price. We can then use the GET /books/ endpoint to retrieve a list of all books, or the GET /books/{id} endpoint to retrieve a single book by its ID.

Example2:

use strict;
use warnings;
use DBIx::Class;
use DBIx::Class::Schema::Loader qw/ make_schema_at /;
use GraphQL::Schema;
use GraphQL::Type::Library -all;
use GraphQL::Execution qw/ execute /;
use GraphQL::Plugin::Convert::DBIC;

# Connect to the MySQL database
my $schema = DBIx::Class::Schema->connect(
    'dbi:mysql:database=books;host=localhost',
    'username',
    'password',
);

# Generate the DBIx::Class schema from the database
make_schema_at(
    'MyApp::Schema',
    { debug => 1 },
    [ 'dbi:mysql:database=books;host=localhost', 'username', 'password' ],
);

# Define the GraphQL schema
my $schema = GraphQL::Schema->from_doc(<<'EOF');
    type Book {
        id: ID!
        title: String!
        author: String!
        price: Float!
    }

    type Query {
        books: [Book]
        book(id: ID!): Book
    }

    type Mutation {
        purchaseBook(id: ID!): Boolean
    }
EOF

# Create the GraphQL resolver
my $resolver = GraphQL::Plugin::Convert::DBIC->new(
    schema => 'MyApp::Schema',
);

# Handle the GraphQL query
my $query = <<'EOF';
    {
        books {
            id
            title
            author
            price
        }
    }
EOF

my $result = execute($schema, $query, $resolver);
print $result->{data}{books};

# Handle the GraphQL mutation
my $mutation = <<'EOF';
    mutation {
        purchaseBook(id: "123") 
    }
EOF

$result = execute($schema, $mutation, $resolver);
print $result->{data}{purchaseBook};


First, we connect to the MySQL database using the DBIx::Class::Schema->connect method, providing the necessary database credentials.

Next, we generate the DBIx::Class schema from the database using the make_schema_at function. This creates a Perl module that represents the database schema and provides an interface for interacting with the data.

We then define the GraphQL schema using the GraphQL::Schema->from_doc method. This defines the types and operations available in our GraphQL API. In this example, we have a Book type with fields for id, title, author, and price. We also have a Query type with a books field to fetch all books and a book field to fetch a specific book by its ID. Additionally, we have a Mutation type with a purchaseBook field to purchase a book by its ID.

To handle the GraphQL queries and mutations, we create a resolver using the GraphQL::Plugin::Convert::DBIC module. This resolver maps the GraphQL queries and mutations to the corresponding DBIx::Class methods.

Finally, we execute the GraphQL query and mutation using the execute function from the GraphQL::Execution module. The results are printed to the console.

Labels: ,

0 Comments:

Post a Comment

Note: only a member of this blog may post a comment.

<< Home