The API returns 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.
If you'd like to be as RESTful as possible, you can use POST, PUT, GET or DELETE request methods. We'll cover that in another post.
The example is an open web service, i.e. it doesn't check for any permissions. You can add that separately (see the multitenant SaaS example).
This example will use PostgreSQL database, but you can use other databases too.
We'll create a new directory ("shop") to keep everything tidy. You can call this directory whatever you want. Then we'll create a new Golf application ("shopping"):
mkdir shop cd shop gg -k shoppingCopied!

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

Create the "customers", "items", "orders" and "orderItems" tables we'll use:
echo "drop table if exists customers; create table if not exists customers (firstName varchar(30), lastName varchar(30), customerID bigserial primary key); drop table if exists items; create table if not exists items (name varchar(30), description varchar(200), itemID bigserial primary key); drop table if exists orders; create table if not exists orders (customerID bigint, orderID bigserial primary key); drop table if exists orderItems; create table if not exists orderItems (orderID bigint, itemID bigint, quantity bigint);" | psql -d db_shoppingCopied!

First thing to do is to let Golf know what database it should use:
echo "user=$(whoami) dbname=db_shopping" > dbCopied!

This tells Golf that a database "db" is setup (with database user being the same as your Operating System user, and the database name being "db_shopping"). Golf makes this easy by letting you use a given database's native format for client configuration, which you're likely to be familiar with. So if you used MariaDB or SQLite for instance, you would have used their native client configuration files.
Here are the source code files.
- Add a new customer
Create file "add-customer.golf" and copy the following to it:
begin-handler /add-customer out-header use content-type "application/json" get-param first_name get-param last_name // Add a customer SQL run-query @db ="insert into customers (firstName, lastName) \ values ('%s', '%s') returning customerID" output customerID : \ first_name, last_name @"<<print-out customerID>>" end-query end-handlerCopied!

- Add new item for sale
Create file "add-item.golf" and copy the following to it:
begin-handler /add-item out-header use content-type "application/json" get-param name get-param description // Add an item to inventory SQL run-query @db ="insert into items (name, description) \ values ('%s', '%s') returning itemID" output item_id : name, description @"<<print-out item_id>>" end-query end-handlerCopied!

- Add an item to an order
Create file "add-to-order.golf" and copy the following to it:
begin-handler /add-to-order out-header use content-type "application/json" get-param order_id get-param item_id get-param quantity // SQL to add an item to an order run-query @db ="insert into orderItems (orderId, itemID, quantity) values ('%s', '%s', '%s')" \ : order_id, item_id, quantity no-loop affected-rows arows @"<<print-out arows>>" end-handlerCopied!

- Create a new order
Create file "create-order.golf" and copy the following to it:
begin-handler /create-order out-header use content-type "application/json" get-param customer_id // SQL to create an order run-query @db ="insert into orders (customerId) \ values ('%s') returning orderID" output order_id : customer_id @"<<print-out order_id>>" end-query end-handlerCopied!

- Delete an order
Create file "delete-order.golf" and copy the following to it:
begin-handler /delete-order out-header use content-type "application/json" get-param order_id begin-transaction run-query @db ="delete from orders where orderID='%s'" : order_id \ no-loop affected-rows order_rows run-query @db ="delete from orderItems where orderID='%s'" : order_id \ no-loop affected-rows item_rows commit-transaction @{ "orders":"<<print-out order_rows>>", "items":"<<print-out item_rows>>" } end-handlerCopied!

- Make a JSON document describing an order
Create file "json-from-order.golf" and copy the following to it:
begin-handler /json_from_order get-param order_id type string get-param curr_order type number get-param order_count type number get-param customer_id type string get-param first_name type string get-param last_name type string @ { @ "orderID": "<<print-out order_id>>", @ "customer": @ { @ "customerID": "<<print-out customer_id>>", @ "firstName": "<<print-out first_name>>", @ "lastName": "<<print-out last_name>>" @ }, @ "items": [ set-number curr_item = 0 // Query to get all items in an order run-query @db ="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 item_count @ { @ "itemID": "<<print-out itemID>>", @ "itemName": "<<print-out itemName>>", @ "itemDescription": "<<print-out itemDescription>>", @ "itemQuantity": "<<print-out itemQuantity>>" // add a comma if there are more items after this set-number curr_item=curr_item+1 if-true curr_item lesser-than item_count @ }, else-if @ } end-if end-query @ ] // add a comma if there are more orders after this set-number curr_order = curr_order+1 if-true curr_order lesser-than order_count @}, else-if @} end-if end-handlerCopied!

- List orders
Create file "list-orders.golf" and copy the following to it:
begin-handler /list-orders out-header use content-type "application/json" get-param order_id set-number curr_order = 0 // Start JSON output @{ "orders": [ if-true order_id not-equal "" // Query just a specific order run-query @db = "select o.orderID, c.customerID, c.firstName, c.lastName \ from orders o, customers c \ where o.customerID=c.customerID and o.orderId='%s'" \ output customer_id, first_name, last_name \ row-count order_count : order_id set-param order_id, curr_order, order_count, customer_id, first_name, last_name call-handler "/json_from_order" end-query else-if // Query to get all orders run-query @db ="select o.orderID, c.customerID, c.firstName, c.lastName \ from orders o, customers c \ where o.customerID=c.customerID order by o.orderId" \ output order_id, customer_id, first_name, last_name \ row-count order_count set-param order_id, curr_order, order_count, customer_id, first_name, last_name call-handler "/json_from_order" end-query end-if // Finish JSON output @ ] @} end-handlerCopied!

- Update an order
Create file "update-order.golf" and copy the following to it:
begin-handler /update-order out-header use content-type "application/json" get-param order_id, item_id, quantity set-number arows // If quantity update is 0, issue SQL to delete an item from order, otherwise update if-true quantity equal "0" run-query @db ="delete from orderItems where orderID='%s' and itemID='%s'" \ : order_id, item_id no-loop affected-rows arows else-if run-query @db ="update orderItems set quantity='%s' where orderID='%s' and itemID='%s'" \ : quantity, order_id, item_id no-loop affected-rows arows end-if @"<<print-out arows>>" end-handlerCopied!

Specify the database "db" (remember we set it up above), and make all handlers public (i.e. they can handle external calls from the outside callers, and not just from within the application):
gg -q --db=postgres:db --publicCopied!

The following is just playing with the API. Golf lets you run your web services from command line, so you can see byte-for-byte exactly what's the response. So the responses below include HTTP header, which in this case is very simple. You can disable HTTP output by specifying "--silent-header" in gg invocations below.
Add new customer:
gg -r --req="/add-customer/first-name=Mike/last-name=Gonzales" --execCopied!

Resulting JSON (showing the ID of a new customer):
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK "1"Copied!

Add an item for sale (showing the ID of a newly added item):
gg -r --req="/add-item/name=Milk/description=Lactose-Free" --execCopied!

The result:
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK "1"Copied!

Add a new order for the customer we created (showing the ID of order):
gg -r --req="/create-order/customer-id=1" --execCopied!

The result:
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK "1"Copied!

Add an item to order, in quantity of 2 (showing number of items added, not the quantity):
gg -r --req="/add-to-order/order-id=1/item-id=1/quantity=2" --execCopied!

The result:
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK "1"Copied!

List orders:
gg -r --req="/list-orders" --execCopied!

Here's the JSON showing current orders (just one in our case):
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK { "orders": [ { "orderID": "1", "customer": { "customerID": "1", "firstName": "Mike", "lastName": "Gonzales" }, "items": [ { "itemID": "1", "itemName": "Milk", "itemDescription": "Lactose-Free", "itemQuantity": "2" } ] } ] }Copied!

Add another item for sale (showing the ID of this new item):
gg -r --req="/add-item/name=Bread/description=Sliced" --execCopied!

The result (showing the ID of a newly added item):
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK "2"Copied!

Add a quantity of 3 of the new item we added (ID of 2):
gg -r --req="/add-to-order/order-id=1/item-id=2/quantity=3" --execCopied!

The result (showing the number of items added, not the quantity):
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK "1"Copied!

List orders again:
gg -r --req="/list-orders" --execCopied!

The result, now there's new items here:
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK { "orders": [ { "orderID": "1", "customer": { "customerID": "1", "firstName": "Mike", "lastName": "Gonzales" }, "items": [ { "itemID": "1", "itemName": "Milk", "itemDescription": "Lactose-Free", "itemQuantity": "2" }, { "itemID": "2", "itemName": "Bread", "itemDescription": "Sliced", "itemQuantity": "3" } ] } ] }Copied!

Update order, by changing the quantity of item (we specify order ID, item ID and the new quantity):
gg -r --req="/update-order/order-id=1/item-id=2/quantity=4" --execCopied!

The result showing number of items updated:
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK "1"Copied!

List orders to show the update:
gg -r --req="/list-orders" --execCopied!

And it shows:
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK { "orders": [ { "orderID": "1", "customer": { "customerID": "1", "firstName": "Mike", "lastName": "Gonzales" }, "items": [ { "itemID": "1", "itemName": "Milk", "itemDescription": "Lactose-Free", "itemQuantity": "2" }, { "itemID": "2", "itemName": "Bread", "itemDescription": "Sliced", "itemQuantity": "4" } ] } ] }Copied!

Delete an order:
gg -r --req="/delete-order/order-id=1" --execCopied!

The result JSON (showing the number of items deleted in it):
Content-Type: application/json Cache-Control: max-age=0, no-cache Status: 200 OK { "orders":"1", "items":"2" }Copied!

You can see that building web services can be easy and fast. More importantly, what matters is also how easy it is to come back to it 6 months later and understand right away what's what. You can try that 6 months from now and see if it's true for you. I'd say chances are pretty good.