Blog

Jon Clausen

March 18, 2016

Spread the word


Share your thoughts

RESTful web services are the rage. APIs for public consumption are everywhere and developers are being tasked daily with the chore of implementing new ones, and ColdBox has a fantastic set of tools for helping you to do just that. As developers, though, we often fall in to the trap of thinking of API's only in terms of delivering data when, in reality, with a bit of foresight and architectural planning, we can create API's that enable conversations with our consumers. In a recent Coldbox Restful Roadshow presentation, two of the "Lessons Learned" I noted, from developing and working with ReSTful APIs over the past few years, included these two recommendations:

  • Use all of the ( HTTP ) Verbs
  • Use all of the ( HTTP Status ) Codes

By taking the time to learn the language of HTTP verbs and status codes, a developer can implement APIs that enable rich, interactive conversations with consumers of their service. Let's say I were to take a trip across the pond from the US to visit the UK. Upon arrival, I check in to my hotel and then make a trip to the pub down the street for a taste of the local brew and, maybe, some grub.

Now, let's re-envision my trip to the corner pub as if it were an ongoing conversation between an API consumer and provider. Again, it's just a corner pub, so that API is fairly limited:

 

THIRSTY DEVELOPER: "Hello! Just flew in to town (and, boy, are my arms tired!). May I see a menu?

HTTP Request:
To start out, I send an OPTIONS request to the pub's representative to find out what's available:
		Request URL: https://cornerpub.co.uk/api
		Accept: application/json
		Request Method: OPTIONS
		
 
CORNER CORNER PUB: "Here's a menu. Have at it, Yank!"

HTTP Response:
The bartender provides me with the pub's options which includes the menu:
		Status Code: 200
		Content Type: application/json
		Response:
		{
			"menu": {
				"food": "/api/food"
				"drinks" "/api/drinks"	
			}

			"order": {
				"POST":{
					"description": "Create an order",
					"href": "/orders",
					"params": {
						"firstName":{
							"required":true,
							"type":"string",
							"description":"Customer's first name"
						},
						"lastName":{
							"required":false,
							"type":"string",
							"description":"Customer's last name"
						},
						"tableNumber":{
							"required":false,
							"type":"integer",
							"description":"The customers table number.  Required if not seated at the bar."
						},
						"barSeating":{
							"required":false,
							"default": false,
							"type":"boolean",
							"description":"Whether the patron is seated at the bar."
						}
					},
				}		
			}
		}
		
 
THIRSTY DEVELOPER: "Great, I'm thirsty let's see what's on the drink menu."

HTTP Request:
		Request URL: https://cornerpub.co.uk/api/drinks
		Accept: application/json
		Request Method: OPTIONS
		
Like turning the page to the drink menu, the API responds again with:
		Status Code: 200
		Content Type: application/json
		Response:
		{	
			"beer": {
				"title": "Beer Menu",
				"params":{
					"roomtemp":{
						"type":"boolean",
						"required":false,
						"description": "Beer which is served at room temperature"
					},
					"cold":{
						"type":"boolean",
						"required":false,
						"description": "Beer which is served cold"
					},
				}
				"href":"/api/drinks/beer"
			},

			"wine": {
				"title": "Wine Menu",
				"params":{
					"red":"Red wine",
					"white":"White wine"
				}
				"href":"/api/drinks/wine"
			},

			"mixed": {
				"title": "Mixed Drinks",
				"params" {}
				"href":"/api/drinks/mixed"
			}
		}
		
 
THIRSTY DEVELOPER: "A cold beer sounds great! Let's see what you have!"

HTTP Request:
		Request URL: https://cornerpub.co.uk/api/drinks/beer?cold=true
		Accept: application/json
		Request Method: GET
		
 
CORNER PUB: "You're in the UK, Yank. We don't have any cold beer."
		Status Code: 204
		Content Type: application/json
		
 
THIRSTY DEVELOPER: "OK, fine. I'm not used to room temperature beer but, since that's all you have, may I see that menu please?"
 
Since there were only two options and one of them didn't have anything, I simply request to see the full list:

HTTP Request:
		Request URL: https://cornerpub.co.uk/api/drinks/beer
		Accept: application/json
		Request Method: GET
		
 
CORNER PUB: "Sure, mate. Here you go - 5 at a time:"
 
Our pub responds with a status of 206 which signifies partial content.
 
Note that rather just dumping an array of items, the pubs response separates the sorting of the products from a top-level hashmap. From a performance standpoint, this makes the consumption, re-sorting, and locating of nested data within the individual results much more efficient for the consumer, as well as for the pub, when it comes time to order.
 
The API response also includes additional information on the recordset and paging:
		Status Code: 206
		Content Type: application/json
		Response:

		{	
			"recordset":{
				"total": 35,
				"limit": 5
				"order" : "brand",
				"orderDirection": "asc",
				"sorted":[
					"2743ce3ea2e04dbf972f8873a7f6e3c7",
					"f6c4198809ce433ea3dce954bd95719b",
					"5dd41ab29f584bc68e2563c67c5d271f",
					"111ca56ba1e14cd3bb707e30c4a816da",
					"82058b6576af41a0a04ea1b457423d32"
				],
				"href": {
					"next":"/drinks/beer?page=2"
				}
			},
			"beers":{
				"82058b6576af41a0a04ea1b457423d32": {
					"name": "Bishop's Farewell",
					"href": "/drinks/beer/82058b6576af41a0a04ea1b457423d32",
					"order": {
						"href":"/order",
						"params":{
							"menutype":"beer",
							"id":"82058b6576af41a0a04ea1b457423d32"
						}
					}
				},
				"111ca56ba1e14cd3bb707e30c4a816da": {
					"name": "Guinness Foreign Extra Stout",
					"href": "/drinks/beer/111ca56ba1e14cd3bb707e30c4a816da",
					"order": {
						"href":"/order",
						"params":{
							"menutype":"beer",
							"id":"111ca56ba1e14cd3bb707e30c4a816da"
						}
					}	
				},
				"f6c4198809ce433ea3dce954bd95719b": {
					"name": "Imperial Brown Stout London 1856",
					"href": "/drinks/beer/f6c4198809ce433ea3dce954bd95719b",
					"order": {
						"href":"/order",
						"params":{
							"menutype":"beer",
							"id":"f6c4198809ce433ea3dce954bd95719b"
						}
					}	
				},
				"2743ce3ea2e04dbf972f8873a7f6e3c7": {
					"name": "Kipling",
					"href": "/drinks/beer/2743ce3ea2e04dbf972f8873a7f6e3c7",
					"order": {
						"href":"/order",
						"params":{
							"menutype":"beer",
							"id":"2743ce3ea2e04dbf972f8873a7f6e3c7"
						}
					}	
				},
				"5dd41ab29f584bc68e2563c67c5d271f": {
					"name": "Landlord Pale Ale",
					"href": "/drinks/beer/5dd41ab29f584bc68e2563c67c5d271f",
					"order": {
						"href":"/order",
						"params":{
							"menutype":"beer",
							"id":"5dd41ab29f584bc68e2563c67c5d271f"
						}
					}	
				}

			}
		}
		
 
THIRSTY DEVELOPER: "Great! I'd like to place an order."

HTTP Request:
		Request URL: https://cornerpub.co.uk/api/orders
		Accept: application/json
		Request Method: POST
		Request Body:

		{
			"firstName":"Jon",
			"barSeating":true
		}

		
 
CORNER PUB: "Very well. Here's your order information:"
 

The pub responds with a status of 201, signifying that the order has been created and provides us with the endpoint for future operations:
 

		Status Code: 201
		Content Type: application/json
		Response:

		{
			"id":"fb5331bf7d9e4dae90f25a190b16037f",
			"href":"/api/orders/fb5331bf7d9e4dae90f25a190b16037f",
		}

		

THIRSTY DEVELOPER: "Perfect. I'd like to order a beer. Tell me how."
 
HTTP Request:
		Request URL: https://cornerpub.co.uk/api/orders/fb5331bf7d9e4dae90f25a190b16037f
		Accept: application/json
		Request Method: OPTIONS
		
 
CORNER PUB: "Blimey!  You sure are needy... **sighs** Here you go:"
		Status Code: 200
		Content Type: application/json
		Response:

		{
			"PUT":{
				"description": "Add items to this order",
				"href": "/api/orders/fb5331bf7d9e4dae90f25a190b16037f",
				"params": {
					"itemType":{
						"required":true,
						"type":"string",
						"description":"The menu type of the item to order"
					},
					"itemId":{
						"required":true,
						"type":"string",
						"description":"The id of the item to order"
					}
				}
			},
			"PATCH"{
				"description":"Update the order",
				"href": "/api/orders/fb5331bf7d9e4dae90f25a190b16037f",
				"params": {
					"closeOrder":{
						"required":false,
						"type":"boolean",
						"description":"Use if closing an order"
					}
				}
			}
			"DELETE":{
				"description": "Cancel this order",
				"href": "/api/orders/fb5331bf7d9e4dae90f25a190b16037f"
			}	
		}

		
 
THIRSTY DEVELOPER: "Thanks so much. I'll have the Kipling, then."
 
HTTP Request:
		Request URL: https://cornerpub.co.uk/api/orders/fb5331bf7d9e4dae90f25a190b16037f
		Accept: application/json
		Request Method: PUT
		Request Body:

		{
			"itemType":"beer",
			"itemId":"2743ce3ea2e04dbf972f8873a7f6e3c7"
		}
		
 
CORNER PUB: [ The bartender's busy and sends a nod, signifying that he's received your order ]: 
 
Since the order cannot be fulfilled immediately, the server reponds with a status of 202, saying that the order has been accepted and provides information on how to check the status of the order.
		Status Code: 202
		Content Type: application/json
		Response:

		{
			"href":"/api/orders/fb5331bf7d9e4dae90f25a190b16037f/status",
			"estimatedWait":120000
		}

		
 
THIRSTY DEVELOPER (after the two minutes specified in the milliseconds wait time have passed): "Any update on my beer?"

I send a HEAD request, to the Pub's API, which requires nothing to be returned but a status code:

HTTP Request:
		Request URL: https://cornerpub.co.uk/api/orders/fb5331bf7d9e4dae90f25a190b16037f/status
		Accept: application/json
		Request Method: HEAD
		
 
CORNER PUB: "Sorry, mate. I had to tap a new keg. Your order is ready now:"

When we sent a status inquiry, the pub now replies with a code of 205, which tells us to reset our content and we're good to go.
		Status Code: 205
		Content Type: application/json
		
THIRSTY DEVELOPER: "Excellent!"

Now we reset our order and the the item has been delivered!

HTTP Request:
		Status Code: 200
		Content Type: application/json
		Response:

		{
			"href":"/api/orders/fb5331bf7d9e4dae90f25a190b16037f",
			"recordset":{
				"total": 1,
				"limit": 5
				"order" : "timeAdded",
				"orderDirection": "asc",
				"sorted":[
					"2743ce3ea2e04dbf972f8873a7f6e3c7"
				],
				"href": {
					"next":"/drinks/beer?page=2"
				}
			},
			"items":{
				"2743ce3ea2e04dbf972f8873a7f6e3c7": {
					"name": "Kipling",
					"href": "/api/orders/fb5331bf7d9e4dae90f25a190b16037f/items/2743ce3ea2e04dbf972f8873a7f6e3c7"
				}

			}
			
		}
		

Obviously, this is a simplified example, but the idea of using a range of verbs and status codes to express the "conversation" between an API and its consumers could be expanded to include additional transactions, options.  In our pub example, we could take additional steps to close the order out and request the check, find out options for ordering food, or add another beer ( we're thirsty! ).

ColdBox's built-in features for handling HTTP request verbs and data responses with expressive status codes make implementing rich "conversational" APIs painless, allowing you, the developer, to focus on the big-picture details of implementation, rather than the writing code to deal with the internals of how it will be accomplished. Happy coding!

Add Your Comment

Recent Entries

Into the Box 2024 - Conference Slides are out!

Into the Box 2024 - Conference Slides are out!

Did you miss the live event? Get a glimpse of the fantastic content our speakers covered by reviewing their slides. Dive into topics like AWS, Cloud, Hosting, Migration, Security, CFML, Java, CommandBox, ContentBox, and more. Don't miss these valuable resources, and join us for upcoming events!

Maria Jose Herrera
Maria Jose Herrera
July 03, 2024
Ortus June 2024 Newsletter!

Ortus June 2024 Newsletter!

Welcome to the latest edition of the Ortus Newsletter! This month, we're excited to bring you highlights from our sessions at CFCamp and Open South Code, as well as a sneak peek into our upcoming events. Discover the latest developments in BoxLang, our dynamic new JVM language, and catch up on all the insightful presentations by our expert team. Let's dive in!

Maria Jose Herrera
Maria Jose Herrera
June 28, 2024
BoxLang June 2024 Newsletter!

BoxLang June 2024 Newsletter!

We're thrilled to bring you the latest updates and exciting developments from the world of BoxLang. This month, we're diving into the newest beta release, introducing a new podcast series, showcasing innovative integrations, and sharing insights from recent events. Whether you're a seasoned developer or just getting started, there's something here for everyone to explore and enjoy.

Maria Jose Herrera
Maria Jose Herrera
June 28, 2024