« 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)
Buy ADMIN Magazine
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Most Popular
Support Our Work
ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.