Build a Web API with Hanami (ConFoo Montreal 2017)

Build a Web API with Hanami (ConFoo Montreal 2017)

Today, when building a web application, you very likely need to provide a web API. In Ruby, both web frameworks Rails or Sinatra are common options.

If you need something simpler and lighter than Rails but that doesn't require to reimplement a lot of common pieces, Hanami is a convenient and trendy option.

I'll present the reasons that pushed me to pick Hanami to develop a web API, and how to test and develop a web API with it.

https://github.com/toch/confoomontreal2017-jsonapi

3f8fcddf7ab5d1bd90b0a0a9adfd6527?s=128

Christophe Philemotte

March 09, 2017
Tweet

Transcript

  1. Build a Web API with Hanami

  2. None
  3. pullreview.com rubybelgium.be euranova.eu

  4. None
  5. toch _toch ibakesoftware.com

  6. Hanami?

  7. A Ruby Web Framework

  8. Light & Simple

  9. Modular

  10. Non Intrusive

  11. Test Friendly ✅

  12. Thread Safe ☂

  13. Why Hanami?

  14. What do we need?

  15. Sinatra?

  16. Rails?

  17. Hanami!

  18. A Web API

  19. GET /api/speakers

  20. $ hanami new confoo \ --database=postgres \ --application-name=api

  21. . ├── apps ├── config ├── db ├── lib ├──

    public └── spec
  22. Gemfile

  23. gem 'rake' gem 'hanami', '~> 1.0.0.beta2' gem 'hanami-model', '~> 1.0.0.beta2'

    gem 'pg'
  24. $ bundle viz -W "test development"

  25. None
  26. None
  27. Container Architecture

  28. apps └── api ├── application.rb ├── config ├── controllers ├──

    templates └── views
  29. # config/environment.rb Hanami::Container.configure do mount Api::Application, at: '/api' end

  30. # config/environment.rb Hanami::Container.configure do mount Api1::Application, at: '/api/v1' mount Api2::Application,

    at: '/api/v2' end
  31. Business logic

  32. lib ├── confoo │ ├── entities │ ├── mailers │

    └── repositories └── confoo.rb
  33. Test First

  34. # spec/api/features/list_speakers_spec.rb require 'api_helper' describe 'List speakers' do it 'is

    successful' do header 'Content-Type', 'application/json;' get '/api/speakers' expect(last_response).must_be :ok? expect(last_response.content_type) .must_include "application/json" end
  35. it 'is empty by default' do header 'Content-Type', 'application/json;' get

    '/api/speakers' expect(last_response.body).must_equal '[]' end end
  36. $ rake # Running: FF 2 runs, 0 assertions, 2

    failures, 0 errors, 0 skips
  37. Default Content & Accept type

  38. # apps/api/application.rb module Api class Application < Hanami::Application configure do

    # ... default_request_format :json default_response_format :json body_parsers :json end end end
  39. One Route

  40. # apps/api/config/routes.rb get '/speakers', to: 'speakers#list'

  41. One Action

  42. $ hanami generate action api speakers#list

  43. # apps/api/controllers/speakers/list.rb module Api::Controllers::Speakers class List include Api::Action accept :json

    def call(params) end end end
  44. No Template One View

  45. # apps/api/views/speakers/list.rb module Api::Views::Speakers class List include Api::View layout false

    def render "[]" end end end
  46. $ rake # Running: .. 2 runs, 3 assertions, 0

    failures, 0 errors, 0 skips
  47. Test First

  48. # spec/api/features/list_speakers_spec.rb #... describe 'List speakers' do # ... describe

    'When speakers are recorded' do let(:repository) { SpeakerRepository.new } before do repository.clear repository.create(Speaker.new(name: 'Christophe Philemotte', twitter: '_toch', talk: 'Build a Web API with Hanami')) end
  49. it 'returns an array of those speakers' do header 'Content-Type',

    'application/json;' get '/api/speakers' expect(last_response.body).must_include "\"name\":\"Christophe Philemotte\",\"twitter\":\"_toch\",\"talk\":\"Build a Web API with Hanami\"" end end end
  50. $ rake # Running: EEE 3 runs, 0 assertions, 0

    failures, 3 errors, 0 skips
  51. Entity & Repository

  52. $ hanami generate model speaker $ hanami generate migration \

    create_speakers
  53. # db/migrations/20161019092946_create_speakers.rb Hanami::Model.migration do change do create_table :speakers do primary_key

    :id column :name, String, null: false column :twitter, String column :talk, String, null: false end end end
  54. $ hanami db create $ hanami db migrate

  55. # lib/confoo/entities/speaker.rb class Speaker < Hanami::Entity end

  56. # lib/confoo/entities/speaker_repository.rb class SpeakerRepository < Hanami::Repository end

  57. Retrieve & Expose

  58. # apps/api/controllers/speakers/list.rb module Api::Controllers::Speakers class List include Api::Action accept :json

    expose :speakers def call(params) @speakers = SpeakerRepository.new.all end end end
  59. Serialize & View

  60. # apps/api/views/speakers/list.rb require 'json' module Api::Views::Speakers class List include Api::View

    layout false def render _raw JSON.dump(speakers.map{ |speaker| speaker.to_h }) end end end
  61. $ rake # Running: … 3 runs, 5 assertions, 0

    failures, 0 errors, 0 skips
  62. Lessons

  63. Stable They played the game

  64. Read The Code Luke

  65. Ruby FTW

  66. Very Friendly Community

  67. More Code But Clear Intent

  68. ❓ toch/confoo2017-jsonapi toch _toch ibakesoftware.com career@euranova.eu