« Previous 1 2 3 4 Next »
Developing RESTful APIs
Late Bloomer
RAML
The RESTful API Modeling Language (RAML) [19] takes a very similar approach to OpenAPI. The initiative is backed by MuleSoft, VMware, and Cisco, among others. RAML promises to support the entire life cycle of an API – from design to rollout – and succeeds in doing so with the help of its toolset and well-prepared documentation [20].
Much like the YAML Swagger Editor, MuleSoft has developed a RAML web API Designer (Figure 3) [21]. The designed API elements can be tried out directly through a mocking service. MuleSoft loads the API design in a web service and executes it. Alternatively, MuleSoft offers an open source development environment named API Workbench [22], although it is under a proprietary license.
RESTful with Ruby and Sinatra
As a simple example, I'll look at a RESTful API for managing a list of names. The RAML API Designer helped with the design. To begin, I designed the API and tested it in the RAML API Designer in a mocking environment to see whether it formally provides the right inputs and outputs. Next, I wrote the first line of code on the basis of RAML specification 0.8 (Listing 1). Thanks to the clear-cut YAML-based presentation, the developer can focus fully on API functionality, which keeps the peculiarities of a language out of the process. A tutorial [23] offers a good introduction to best practices for developing a REST API.
Listing 1
api.raml
01 #%Raml 0.8 02 title: Contacts 03 version: 0.1 04 #baseUri: http://www.rve.com/contactshelf 05 baseUri: https://mocksvc.mulesoft.com/mocks/d4c4356f-0508-4277-9060-00ab6dd36e9b/contactshelf 06 /contacts: 07 get: 08 description: Retrieve list of existing contacts 09 responses: 10 200: 11 body: 12 application/json: 13 example: | 14 { 15 "data": [ 16 { 17 "id": "1", 18 "name": "Rheik", 19 "link": "http://localhost:4567/contacts/Rheik" 20 }, 21 { 22 "id": "2", 23 "name": "Paul", 24 "link": "http://localhost:4567/contacts/Paul" 25 }, 26 { 27 "id": "3", 28 "name": "Emil", 29 "link": "http://localhost:4567/contacts/Emil" 30 } 31 ], 32 "success": true, 33 "status": 200 34 } 35 /{name}: 36 get: 37 description: Retrieve a specific contact 38 responses: 39 200: 40 body: 41 application/json: 42 example: | 43 { 44 "data": { 45 "id": "1", 46 "name": "Rheik", 47 "link": "http://localhost:4567/contacts/Rheik" 48 }, 49 "success": true, 50 "status": 200 51 } 52 /new: 53 post: 54 description: Add a new contact 55 body: 56 application/json: 57 example: | 58 { 59 "data": { 60 "name": "Franz", 61 "link": "http://localhost:4567/contacts/Franz" 62 } 63 } 64 responses: 65 200: 66 body: 67 application/json: 68 example: | 69 { "message": "Contact transferred correctly." } 70 /{id}: 71 delete: 72 description: Delete a specific contact 73 responses: 74 200: 75 body: 76 application/json: 77 example: | 78 { "message": "Contact id = <<resourcePathName>> deleted." } 79 404: 80 body: 81 application/json: 82 example: | 83 { "message": "Contact id = <<resourcePathName>> not found." }
The data for the service is stored in a SQLite database. The script in Listing 2 fills the database with three sample entries from the command line and then lists the entries.
Listing 2
SQLite Database with Sample Entries
$ sqlite3 contacts.sqlite "CREATE TABLE contacts (id INTEGER PRIMARY KEY, name, link);" $ sqlite3 contacts.sqlite "INSERT INTO contacts (name, link) VALUES ('Rheik', 'http://localhost:4567/contacts/Rheik');" $ sqlite3 contacts.sqlite "INSERT INTO contacts (name, link) VALUES ('Paul', 'http://localhost:4567/contacts/Paul');" $ sqlite3 contacts.sqlite "INSERT INTO contacts (name, link) VALUES ('Emil', 'http://localhost:4567/contacts/Emil');" $ sqlite3 contacts.sqlite "SELECT * FROM contacts;" 1|Rheik|http://localhost:4567/contacts/Rheik 2|Paul|http://localhost:4567/contacts/Paul 3|Emil|http://localhost:4567/contacts/Emil $
The Ruby script in Listing 3 shows the program for a Sinatra server that provides a simple REST API, which is only RESTful in its approach because the JSON format used in the example is not standardized, referenced by a schema, or genuinely based on hypertext. The corresponding hypermedia schema would be used here, but it was left out of the example for the sake of clarity.
Listing 3
contacts.rb
01 require 'rubygems' 02 require 'sinatra' 03 require 'sinatra/json' 04 require 'uri' 05 require 'active_record' 06 07 ActiveRecord::Base.establish_connection( 08 :adapter => 'sqlite3', 09 :database => 'contacts.sqlite' 10 ) 11 12 class Contact < ActiveRecord::Base 13 end 14 15 get '/contacts' do 16 @contacts = Contact.all 17 @response_message = {data: @contacts, success: true, status: 200} 18 json @response_message 19 end 20 21 get '/contacts/:name' do 22 @name = params['name'] 23 @contact = Contact.where(name: @name).first 24 @response_message = {data: [@contact], success: true, status: 200} 25 json @response_message 26 end 27 28 post '/contacts/new' do 29 @data = JSON.parse(request.body.read)['data'] 30 puts @data.inspect 31 if @data['name'] and @data['link'] then 32 @contact = Contact.new(name: @data['name'], link: @data['link']) 33 @contact.save 34 end 35 @response_message = { "message": "Contact #{@data['name']} created." } 36 json @response_message 37 end 38 39 delete '/contacts/:id' do 40 @id = params['id'] 41 @contact = Contact.find_by_id(@id) 42 if @contact then 43 Contact.delete(@contact.id) 44 @response_message = { "message": "Contact id = #{@contact.id} deleted." } 45 else 46 @response_message = { "message": "Contact id = #{@contact.id} not found." } 47 end 48 json @response_message 49 end 50 51 options '/' do 52 # Currently supported HTTP verbs 53 response.headers["Allow"] = "HEAD,GET,POST,DELETE,OPTIONS" 54 204 55 end
Easy Access
Assuming you are in the contacts.sqlite
database directory, the Sinatra server can be launched by typing ruby contacts.rb
. The client role is played by the versatile curl on the command line. To list the existing entries in the database, curl accesses http://localhost:4567/contacts
, which then returns the data in JSON format.
The -v
option displays all the HTTP parameters, from which you can specifically tell that the content type is application/json
(Listing 4). Curl picks this up with curl "http://localhost:4567/contacts/Rheik"
, which corresponds to the get '/contacts/:name'
method in the Sinatra script and /{name}:
in the RAML draft.
Listing 4
Curl Access
01 $ curl -v "http://localhost:4567/contacts" 02 * Trying ::1... 03 * TCP_NODELAY set 04 * Connected to localhost (::1) port 4567 (#0) 05 > GET /contacts HTTP/1.1 06 > Host: localhost:4567 07 > User-Agent: curl/7.51.0 08 > Accept: */* 09 > 10 < HTTP/1.1 200 OK 11 < Content-Type: application/json 12 < Content-Length: 382 13 < X-Content-Type-Options: nosniff 14 < Server: WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) 15 < Date: Thu, 30 Mar 2017 18:48:49 GMT 16 < Connection: Keep-Alive 17 < 18 * Curl_http_done: called premature == 0 19 * Connection #0 to host localhost left intact 20 {"data":[{"id":1,"name":"Rheik","link":"http://localhost:4567/contacts/Rheik"},{"id":2,"name":"Paul","link":"http://localhost:4567/contacts/Paul"},{"id":3,"name":"Emil","link":"http://localhost:4567/contacts/Emil"}],"success":true,"status":200} 21 $
Curl creates a new entry with a call to the POST
method (Listing 5), but the program here breaks with REST logic. A more intuitive approach would be a POST
to the URL of the contact resource http://localhost:4567/contacts/Jim
; however, many developers expect a fixed path (/new
), if the resource does not yet exist.
Listing 5
Curl and POST
$ curl -H "Content-Type: application/json" -X POST -d '{"data": {"name": "Jim", "link": "http:://localhost:4567/contacts/Jim"}}' http://localhost:4567/contacts/new {"message":"Successfully created contact Jim."}
It would be formally correct to send the POST
to /contacts
. The best idea is for developers to find a solution that catches the eye.
The call
curl -H "Content-Type: application/json" -X DELETE http://localhost:4567/contacts/6
deletes the entry with ID 6
. In contrast to the name, the ID is unique.
With the options
method (Listing 3, lines 51-55), the server tells the user, on receiving an appropriate request, which HTTP verbs it supports ("Allow"
). The answer requires the curl -i
parameter, because the output will not otherwise appear (Listing 6).
Listing 6
Querying Options
01 $ curl -i -X OPTIONS http://localhost:4567/ 02 HTTP/1.1 204 No Content 03 Allow: HEAD,GET,POST,DELETE,OPTIONS 04 X-Content-Type-Options: nosniff 05 Server: WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) 06 Date: Thu, 30 Mar 2017 7:11:56 PM GMT 07 Connection: Keep-Alive 08 $
« Previous 1 2 3 4 Next »
Buy this article as PDF
(incl. VAT)