$30 off During Our Annual Pro Sale. View Details »

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

Christophe Philemotte

March 09, 2017
Tweet

More Decks by Christophe Philemotte

Other Decks in Programming

Transcript

  1. Build a Web API
    with Hanami

    View Slide

  2. View Slide

  3. pullreview.com
    rubybelgium.be
    euranova.eu

    View Slide

  4. View Slide

  5. toch
    _toch
    ibakesoftware.com

    View Slide

  6. Hanami?

    View Slide

  7. A Ruby Web
    Framework

    View Slide

  8. Light &
    Simple

    View Slide

  9. Modular

    View Slide

  10. Non Intrusive

    View Slide

  11. Test Friendly

    View Slide

  12. Thread Safe

    View Slide

  13. Why Hanami?

    View Slide

  14. What do we need?

    View Slide

  15. Sinatra?

    View Slide

  16. Rails?

    View Slide

  17. Hanami!

    View Slide

  18. A Web API

    View Slide

  19. GET /api/speakers

    View Slide

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

    View Slide

  21. .
    ├── apps
    ├── config
    ├── db
    ├── lib
    ├── public
    └── spec

    View Slide

  22. Gemfile

    View Slide

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

    View Slide

  24. $ bundle viz -W "test development"

    View Slide

  25. View Slide

  26. View Slide

  27. Container Architecture

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. Business logic

    View Slide

  32. lib
    ├── confoo
    │ ├── entities
    │ ├── mailers
    │ └── repositories
    └── confoo.rb

    View Slide

  33. Test First

    View Slide

  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

    View Slide

  35. it 'is empty by default' do
    header 'Content-Type', 'application/json;'
    get '/api/speakers'
    expect(last_response.body).must_equal '[]'
    end
    end

    View Slide

  36. $ rake
    # Running:
    FF
    2 runs, 0 assertions, 2 failures, 0 errors, 0 skips

    View Slide

  37. Default Content &
    Accept type

    View Slide

  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

    View Slide

  39. One Route

    View Slide

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

    View Slide

  41. One Action

    View Slide

  42. $ hanami generate action api speakers#list

    View Slide

  43. # apps/api/controllers/speakers/list.rb
    module Api::Controllers::Speakers
    class List
    include Api::Action
    accept :json
    def call(params)
    end
    end
    end

    View Slide

  44. No Template
    One View

    View Slide

  45. # apps/api/views/speakers/list.rb
    module Api::Views::Speakers
    class List
    include Api::View
    layout false
    def render
    "[]"
    end
    end
    end

    View Slide

  46. $ rake
    # Running:
    ..
    2 runs, 3 assertions, 0 failures, 0 errors, 0 skips

    View Slide

  47. Test First

    View Slide

  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

    View Slide

  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

    View Slide

  50. $ rake
    # Running:
    EEE
    3 runs, 0 assertions, 0 failures, 3 errors, 0 skips

    View Slide

  51. Entity & Repository

    View Slide

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

    View Slide

  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

    View Slide

  54. $ hanami db create
    $ hanami db migrate

    View Slide

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

    View Slide

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

    View Slide

  57. Retrieve & Expose

    View Slide

  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

    View Slide

  59. Serialize & View

    View Slide

  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

    View Slide

  61. $ rake
    # Running:

    3 runs, 5 assertions, 0 failures, 0 errors, 0 skips

    View Slide

  62. Lessons

    View Slide

  63. Stable
    They played the game

    View Slide

  64. Read
    The Code Luke

    View Slide

  65. Ruby FTW

    View Slide

  66. Very Friendly
    Community

    View Slide

  67. More Code But
    Clear Intent

    View Slide


  68. toch/confoo2017-jsonapi
    toch
    _toch
    ibakesoftware.com
    [email protected]

    View Slide