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

ColdBox 7.2.0 Released

ColdBox 7.2.0 Released

ColdBox, a widely used development platform for ColdFusion (CFML), has unveiled version 7.2. Packed with compelling new features, bug fixes, and enhancements, this release is designed to empower developers by boosting productivity, refining scheduled task capabilities, and enhancing the overall reliability and efficiency of application development. This article will delve into the key highlights of ColdBox 7.2 and elucidate how these advancements can positively impact developers in their daily coding endeavors.

Luis Majano
Luis Majano
November 20, 2023
Into the Box 2023 Series on CFCast

Into the Box 2023 Series on CFCast

Excitement is in the air as we unleash the highly anticipated ITB 2023 series exclusively for our valued CFCast subscribers – and the best part? It's FREE for CFCast members! Now is the perfect time if you haven't joined the CFCast community yet. Plus, we've got an incredible End-of-Year deal that's too good to miss

Maria Jose Herrera
Maria Jose Herrera
November 20, 2023
Ortus Deals are Finally Here!

Ortus Deals are Finally Here!

The much-anticipated Ortus End-of-the-Year Sale has arrived, and it's time to elevate your development experience! Whether you're a seasoned developer, a tech enthusiast, or someone on the lookout for top-notch projects, Ortus has something special in store for you. Brace yourself for incredible discounts across a wide array of products and services, including Ortus annual events, books, cutting-edge services, and more.

Maria Jose Herrera
Maria Jose Herrera
November 15, 2023