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

BoxLang 1.0.0 Beta 23 Launched

BoxLang 1.0.0 Beta 23 Launched

The latest release of BoxLang, Beta 23, marks a significant step forward in our journey to create the ultimate dynamic language for the JVM. Packed with powerful new features, important bug fixes, and thoughtful optimizations, this update is designed to make your development experience smoother, faster, and more reliable, especially after now starting to take 100s of comments and bug reports from our community.

Luis Majano
Luis Majano
November 23, 2024
TestBox Latest Updates and News!

TestBox Latest Updates and News!

We’re thrilled to have launched the new TestBox website and TestBox 6.0! If you haven’t had a chance to explore yet, visit TestBox to discover updated documentation, powerful resources, and features that make testing more efficient than ever.

Maria Jose Herrera
Maria Jose Herrera
November 21, 2024
Is Your ColdFusion Application Ready for the Future?

Is Your ColdFusion Application Ready for the Future?

In a rapidly evolving digital world, maintaining performance, security, and scalability for ColdFusion applications is more challenging than ever. Whether you're using Lucee or Adobe ColdFusion, legacy systems can become a bottleneck for growth, innovation, and user satisfaction. The need to future-proof your ColdFusion applications has never been more critical.

But where do you start?


The Hidden Costs of an Outdated ColdFusion Application

As you...

Cristobal Escobar
Cristobal Escobar
November 21, 2024