Vely logo install
examples
documentation

Vely 15.2.0 released on Jan 18, 2023
Articles updated Jan 17, 2023

Example: shopping



Description:


This is a shopping web service REST API with basic functions:
The APIs return a valid JSON reply, even if it's just a single string (such as created ID for a customer, item or order). Listing an order returns a JSON document showing the order details.

The example demostrates usage of REST methods (POST, PUT, GET, DELETE) as well as construction and use of REST URLs, and the code that implements the API.

In a nutshell: 11 source files, 212 lines of code; PostgreSQL; web browser; Apache; REST API; Application path; Unix sockets;

Screenshots of application


Add a customer (named Diana Vega). "Customer ID" is the result JSON text (in this case "3"):

Vely

Add two items ("chocolate bar" and "swiss cheese"). "Item ID" is the result JSON text for each:

Vely

Add an order for customer created by specifying "Customer ID" (in this case it's "3"):

Vely

Add two items to order created by specifying "Order ID" (in this case "5"). The two items are the ones created with "Item ID"s of "3" and "4", and quantities of items ordered are "2" and "1" respectively:

Vely

Update an order by specifying "Order ID" (in this case "5"). The items is specified with "Item ID" (in this case "4") and the quantity is updated to "3":

Vely

Delete an order with "Order ID" (in this case "3"). The result is JSON text specifying number of orders deleted (in this case "1"), and the number of items in it (in this case "0"):

Vely

List a single order by using "Order ID" (in this case "5"). JSON response contains customer and items information:

Vely

List all orders. The JSON response contains customer and items information, and in this case there are two orders:

Vely

Deletes a line item (i.e. all items of the same kind) in an order. In this case "Item ID" specifies the item ("4") in an order given by "Order ID" ("5"). The result is the number of line items deleted:

Vely


Setup prerequisites


Install Vely - you can use standard packaging tools such as apt, dnf, pacman or zypper.

Because they are used in this example, you will need to install Apache as a web server and PostgreSQL as a database.

After installing Vely, turn on syntax highlighting in vim if you're using it:

vv -m


Get the source code


The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:

tar xvf $(vv -o)/examples/shopping.tar.gz
cd shopping


Setup application


The very first step is to create an application. The application will be named "shopping", but you can name it anything (if you do that, change it everywhere). It's simple to do with vf:

sudo vf -i -u $(whoami) shopping

This will create a new application home (which is "/var/lib/vv/shopping") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.

Setup the database


Before any coding, you need some place to store the information used by the application. First, you will create PostgreSQL database "db_shopping". You can change the database name, but remember to change it everywhere here. And then, you will create database objects in the database.

Note the example here uses peer-authentication, which is the default on all modern PostgreSQL installations - this means the database user name is the same as the Operating System user name.

Execute the following in PostgreSQL database as root (using psql utility):

echo "create user $(whoami);
create database db_shopping with owner=$(whoami);
grant all on database db_shopping to $(whoami);
\q
" | sudo -u postgres psql

Next, login to database db_shopping and create the database objects for the application:

psql -d db_shopping -f setup.sql


Connect Vely to a database


In order to let Vely know where your database is and how to log into it, you will create database_config_file named "db_shopping". This name doesn't have to be "db_shopping", rather it can be anything - this is the name used in actual database statements in source code (like run-query), so if you change it, make sure you change it everywhere. Create it:

echo "user=$(whoami) dbname=db_shopping"  > db_shopping

The above is a standard postgres connection string that describes the login to the database you created. Since Vely uses native PostgreSQL connectivity, you can specify any connection string that your database lets you.

Build application


Use vv utility to make the application:

vv -q --db=postgres:db_shopping --path="/api/v1/shopping"

Note usage of --db option to specify PostgreSQL database and the database configuration file name.

--path is used to specify the application path, see request_URL.

Start your application server


To start the application server for your web application use vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):

vf -w 3 shopping

This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:

vf shopping

See vf for more options to help you achieve best performance.

To stop your application server:

vf -m quit shopping


Setup web server


This shows how to connect your application listening on a Unix socket (started with vf) to Apache web server.

- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done only once:
- Step 2:
Edit the Apache configuration file:
Add this to the end of file ("/api/v1/shopping" is the application path (see request_URL) and "shopping" is your application name):

ProxyPass "/api/v1/shopping" unix:///var/lib/vv/shopping/sock/sock|fcgi://localhost/shopping

- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:

sudo systemctl restart apache2

On Fedora systems (like RedHat) and Arch Linux:

sudo systemctl restart httpd



Access application server from the browser


Use the following URL(s) to access your application server from a web client like browser (see request_URL). Use actual IP or web address instead of 127.0.0.1 if different.

Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see ufw, firewall-cmd etc.
Here is REST API for the application.

Substitute the loopback "127.0.0.1" with the IP or web address of your server.

To add a customer, use the POST URL to create new "customer" resource:

curl -X POST  "http://127.0.0.1/api/v1/shopping/customers/first-name/<first name>/last-name/<last name>"

for example:

curl -X POST  "http://127.0.0.1/api/v1/shopping/customers/first-name/Mia/last-name/Beltran"

The return value is a JSON document containing a single value, and that is <customer ID> of a newly created customer.

To add an item to the list of inventory items, use the POST URL to create new "item" resource:

curl -X POST  "http://127.0.0.1/api/v1/shopping/items/name/<item name>?description=<item description>"

for example:

curl -X POST  "http://127.0.0.1/api/v1/shopping/items/name/rice-pudding?description=Delicious"

The return value is a JSON document containing a single value, and that is <item ID> of a newly created item.

To create an order, use the following POST URL to create a new "order" resource, and use <customer ID> previously obtained:

curl -X POST "http://127.0.0.1/api/v1/shopping/orders/customer-id/<customer ID>"

for example:

curl -X POST "http://127.0.0.1/api/v1/shopping/orders/customer-id/1"

The return value is a JSON document containing a single value, and that is <order ID> of a newly created order.

To add an item to an order, use the following POST URL to create a new "order/item" resource, and use <order ID> and <item ID> previously obtained:

curl -X POST "http://127.0.0.1/api/v1/shopping/orders/order-id/<order ID>/item-id/<item ID>?quantity=<item quantity>"

for example:

curl -X POST "http://127.0.0.1/api/v1/shopping/orders/order-id/1/item-id/1?quantity=2"

The return value is a JSON document containing a single value, and that is the number of line items created ("1" or "0").

To update an order (meaning change the number of items), use the following PUT URL, and use <order ID> and <item ID> previously obtained:

curl -X PUT "http://127.0.0.1/api/v1/shopping/orders/order-id/<order ID>/item-id/<item ID>?quantity=<item quantity>"

for example:

curl -X PUT "http://127.0.0.1/api/v1/shopping/orders/order-id/1/item-id/1?quantity=3"

The return value is a JSON document containing a single value, and that is the number of line items updated ("1" or "0"). Per REST methodology, this is an idemopotent operation, i.e. it can be repeated any number of times with the same result.

To delete an item from order, use the following DELETE URL, and use <order ID> and <item ID> previously obtained:

curl -X DELETE "http://127.0.0.1/api/v1/shopping/orders/order-id/<order ID>/item-id/<item ID>"

for example:

curl -X DELETE "http://127.0.0.1/api/v1/shopping/orders/order-id/1/item-id/2"

The return value is a JSON document containing a single value, and that is the number of line items delete ("1" or "0").

To list all orders, use the following GET URL:

curl -X GET "http://127.0.0.1/api/v1/shopping/orders"

The return value is a JSON document with all orders, including customer information, as well as items, their descriptions and quantities.

To list a specific order, use the following GET URL:

curl -X GET "http://127.0.0.1/api/v1/shopping/orders/order-id/<order ID>"

for example:

curl -X GET "http://127.0.0.1/api/v1/shopping/orders/order-id/1"

The return value is a JSON document with a specific order, including customer information, as well as items, their descriptions and quantities.

To delete a specific order, use the following DELETE URL:

curl -X DELETE "http://127.0.0.1/api/v1/shopping/orders/order-id/<order id>"

for example:

curl -X DELETE "http://127.0.0.1/api/v1/shopping/orders/order-id/1"

The return value is a JSON document with a number of orders and number of items deleted.

Files:


You are now done with the example! What follows are the source files in this project so you can examine how it works:

Customers resource (customers.vely)


This implements REST API for a customer.

#include "vely.h"

void customers()
{
    get-req method to define req_method

    if (req_method == VV_POST) add_customer ();
}


Orders resource (orders.vely)


This implements REST API for an order.

#include "vely.h"

void orders()
{
    out-header use content-type "application/json"

    get-req method to define req_method

    if (req_method == VV_POST) {
        input-param item_id
        if (item_id[0] == 0) create_order (); else add_to_order();
    }
    else if (req_method == VV_PUT) update_order ();
    else if (req_method == VV_GET) list_orders ();
    else if (req_method == VV_DELETE) {
        input-param item_id
        if (item_id[0] == 0) delete_order (); else {
            set-input "quantity" = "0"
            update_order ();
        }
    }
}


Items resource (items.vely)


This implements REST API for an item.

#include "vely.h"

void items()
{
    out-header use content-type "application/json"

    get-req method to define req_method

    if (req_method == VV_POST) add_item ();
}


Add a customer (add_customer.vely)


This will add a new customer. Removing a customer is not included, and it should remove all its orders.

#include "vely.h"

void add_customer()
{
    out-header use content-type "application/json"

    input-param first_name
    input-param last_name
    // Add a customer SQL
    run-query @db_shopping ="insert into customers (firstName, lastName) \
            values ('%s', '%s') returning customerID" output define customerID : \
            first_name, last_name
        @"<<p-out customerID>>"
    end-query
}


Add an item to inventory (add_item.vely)


Here an item is added to invetory available for sale.

#include "vely.h"

void add_item()
{
    out-header use content-type "application/json"

    input-param name
    input-param description
    // Add an item to inventory SQL
    run-query @db_shopping ="insert into items (name, description) \
        values ('%s', '%s') returning itemID" output itemID : name, description
        query-result itemID to define item_id
        @"<<p-out item_id>>"
    end-query
}


Create an order (create_order.vely)


This creates a new order, ready to have items added to it.

#include "vely.h"

void create_order()
{
    out-header use content-type "application/json"

    input-param customer_id
    // SQL to create an order
    run-query @db_shopping ="insert into orders (customerId) \
        values ('%s') returning orderID" output orderID : customer_id
        query-result orderID to define order_id
        @"<<p-out order_id>>"
    end-query
}


Add item to order (add_to_order.vely)


Add an item to existing order.

#include "vely.h"

void add_to_order()
{
    out-header use content-type "application/json"

    input-param order_id
    input-param item_id
    input-param quantity
    // SQL to add an item to an order
    run-query @db_shopping ="insert into orderItems (orderId, itemID, quantity) values  ('%s', '%s', '%s')" \
        : order_id, item_id, quantity no-loop affected-rows define arows
    @"<<p-num arows>>"
}


Update item quantity in an order (update_order.vely)


If the quantity update is 0, the item is deleted from order, otherwise the quantity is updated.

#include "vely.h"

void update_order()
{
    out-header use content-type "application/json"

    input-param order_id
    input-param item_id
    input-param quantity
    num arows;
    // If quantity update is 0, issue SQL to delete an item from order, otherwise update 
    if (!strcmp (quantity, "0")) {
        run-query @db_shopping ="delete from orderItems where orderID='%s' and itemID='%s'" \
            : order_id, item_id no-loop affected-rows arows
    } else {
        run-query @db_shopping ="update orderItems set quantity='%s' where orderID='%s' and itemID='%s'" \
            : quantity, order_id, item_id no-loop affected-rows arows
    }
    @"<<p-num arows>>"
}


List orders as JSON (list_orders.vely)


All orders are listed along with customer ID and name, and under each order are the items with their names, descriptions and quantity of items ordered.

#include "vely.h"
#include "shopping.h"

void list_orders()
{
    out-header use content-type "application/json"

    input-param order_id

    num curr_order = 0;

    // Start JSON output
    @{ "orders": [

    if (order_id[0] != 0) {
        // Query just a specific order
        run-query @db_shopping = "select o.orderID, c.customerID, c.firstName, c.lastName \
                from orders o, customers c \
                where o.customerID=c.customerID and o.orderId='%s'" \
                output define customer_id, first_name, last_name \
                row-count define order_count : order_id
            _json_from_order (order_id, curr_order, order_count, customer_id,
                first_name, last_name);
        end-query
    } else {
        // Query to get all orders
        run-query @db_shopping ="select o.orderID, c.customerID, c.firstName, c.lastName \
                from orders o, customers c \
                where o.customerID=c.customerID order by o.orderId" \
                output define order_id, customer_id, first_name, last_name \
                row-count define order_count
            _json_from_order (order_id, curr_order, order_count, customer_id,
                first_name, last_name);
        end-query
    }

    // Finish JSON output
    @   ]
    @}
}


Represent order as JSON (_json_from_query.vely)


This will take order information from the list of orders (as directed by GET REST API for either all orders or a specific one), and find all items within an order. The JSON text is output.

#include "vely.h"
#include "shopping.h"

void _json_from_order (const char *order_id, num curr_order, num order_count,
    const char *customer_id, const char *first_name, const char *last_name)
{
    @   {
    @       "orderID": "<<p-out order_id>>",
    @       "customer":
    @       {
    @           "customerID": "<<p-out customer_id>>",
    @           "firstName": "<<p-out first_name>>",
    @           "lastName": "<<p-out last_name>>"
    @       },
    @       "items": [
    num curr_item = 0;
    // Query to get all items in an order
    run-query @db_shopping ="select i.itemID, t.name, t.description, i.quantity \
            from orderItems i, items t where i.orderID='%s' \
                and t.itemID=i.itemID" \
            output itemID, itemName, itemDescription, itemQuantity : order_id \
            row-count define item_count
        @       {
        @           "itemID": "<<query-result itemID>>",
        @           "itemName": "<<query-result itemName>>",
        @           "itemDescription": "<<query-result itemDescription>>",
        @           "itemQuantity": "<<query-result itemQuantity>>"
        // add a comma if there are more items after this
        @       }<<p-out ++curr_item < item_count ? ",":"">>
    end-query
    @   ]
    // add a comma if there are more orders after this
    @   }<<p-out ++curr_order < order_count ? ",":"">>
}


Include file (shopping.h)


This file has a function declaration for _json_from_query() so it can be used in multiple .vely files.

#include "vely.h"

void _json_from_order (const char *order_id, num curr_order, num order_count,
    const char *customer_id, const char *first_name, const char *last_name);


See also:


Examples ( example_cookies   example_create_table   example_docker   example_file_manager   example_form   example_hello_world   example_json   example_multitenant_SaaS   examples   example_sendmail   example_shopping   example_stock   example_utility   example_write_report  )  SEE ALL (documentation)



Copyright (c) 2022 DaSoftver LLC. Vely is a trademark of Dasoftver LLC. The software and information herein are provided "AS IS" and without any warranties or guarantees of any kind. Vely elephant logo (c) 2022 DaSoftver LLC. This web page is licensed under CC-BY-SA-4.0.