How to build a Web Service API
This article will show a simple shopping web API with basic functions, such as adding customers, items and orders, as well as updating and deleting them. It's easy to write and easy to understand with Golf; that's the reason code doesn't have much in a way of comments - there's no need.
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 article-notes-postgres).
This example will use PostgreSQL database, but you can use other databases too.
Keep this project in its own directory
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 shopping
Copied!
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 psql
Copied!
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_shopping
Copied!
Tell Golf about your database
First thing to do is to let Golf know what database it should use:
echo "user=$(whoami) dbname=db_shopping" > db
Copied!
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.
Source code for web service
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
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-handler
Copied!
- 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
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-handler
Copied!
- 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
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-handler
Copied!
- 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
run-query @db ="insert into orders (customerId) \
values ('%s') returning orderID" output order_id : customer_id
@"<<print-out order_id>>"
end-query
end-handler
Copied!
- 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-handler
Copied!
- 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
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>>"
set-number curr_item=curr_item+1
if-true curr_item lesser-than item_count
@ },
else-if
@ }
end-if
end-query
@ ]
set-number curr_order = curr_order+1
if-true curr_order lesser-than order_count
@},
else-if
@}
end-if
end-handler
Copied!
- 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 default-value ""
set-number curr_order = 0
@{ "orders": [
if-true order_id not-equal ""
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
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
@ ]
@}
end-handler
Copied!
- 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-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-handler
Copied!
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 --public
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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" --exec
Copied!
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.
Articles
article-capi
article-cookies
article-debug
article-distributed
article-encryption
article-fetch-web-page
article-fifo
article-file-manager
article-hello-server
article-hello-world
article-hello-world-service
article-hello-world-service-web
article-how-to-create-golf-application
article-json
article-language
article-mariadb
article-memory-safety
article-memory-safety-web
article-notes-postgres
article-random
article-regex
article-remote-call
article-request-function
article-security
article-sendmail
article-server
article-shopping
article-sqlite
article-statements
article-status-check
article-tree
article-tree-web
article-vim-coloring
article-web-framework-for-c-programming-language
article-what-is-golf
article-what-is-web-service
See all
documentation
Copyright (c) 2019-2025 Gliim LLC. All contents on this web site is "AS IS" without warranties or guarantees of any kind.