├── Gemfile ├── Gemfile.lock ├── README.md ├── api ├── books.rb └── root.rb ├── config.ru └── spec ├── api_spec.rb └── spec_helper.rb /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'grape' 3 | gem 'grape-swagger' 4 | 5 | group :development, :test do 6 | gem 'rspec' 7 | gem 'airborne' 8 | gem 'pry' 9 | end -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.2.4) 5 | i18n (~> 0.7) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | airborne (0.1.20) 11 | activesupport (>= 3.0.0) 12 | rack-test (~> 0.6, >= 0.6.2) 13 | rest-client (~> 1.7, >= 1.7.3) 14 | rspec (~> 3.1, >= 3.1.0) 15 | axiom-types (0.1.1) 16 | descendants_tracker (~> 0.0.4) 17 | ice_nine (~> 0.11.0) 18 | thread_safe (~> 0.3, >= 0.3.1) 19 | builder (3.2.2) 20 | coderay (1.1.0) 21 | coercible (1.0.0) 22 | descendants_tracker (~> 0.0.1) 23 | descendants_tracker (0.0.4) 24 | thread_safe (~> 0.3, >= 0.3.1) 25 | diff-lcs (1.2.5) 26 | domain_name (0.5.25) 27 | unf (>= 0.0.5, < 1.0.0) 28 | equalizer (0.0.11) 29 | grape (0.13.0) 30 | activesupport 31 | builder 32 | hashie (>= 2.1.0) 33 | multi_json (>= 1.3.2) 34 | multi_xml (>= 0.5.2) 35 | rack (>= 1.3.0) 36 | rack-accept 37 | rack-mount 38 | virtus (>= 1.0.0) 39 | grape-entity (0.4.8) 40 | activesupport 41 | multi_json (>= 1.3.2) 42 | grape-swagger (0.10.2) 43 | grape (>= 0.8.0) 44 | grape-entity 45 | hashie (3.4.2) 46 | http-cookie (1.0.2) 47 | domain_name (~> 0.5) 48 | i18n (0.7.0) 49 | ice_nine (0.11.1) 50 | json (1.8.3) 51 | method_source (0.8.2) 52 | mime-types (2.99) 53 | minitest (5.8.3) 54 | multi_json (1.11.2) 55 | multi_xml (0.5.5) 56 | netrc (0.11.0) 57 | pry (0.10.1) 58 | coderay (~> 1.1.0) 59 | method_source (~> 0.8.1) 60 | slop (~> 3.4) 61 | rack (1.6.4) 62 | rack-accept (0.4.5) 63 | rack (>= 0.4) 64 | rack-mount (0.8.3) 65 | rack (>= 1.0.0) 66 | rack-test (0.6.3) 67 | rack (>= 1.0) 68 | rest-client (1.8.0) 69 | http-cookie (>= 1.0.2, < 2.0) 70 | mime-types (>= 1.16, < 3.0) 71 | netrc (~> 0.7) 72 | rspec (3.3.0) 73 | rspec-core (~> 3.3.0) 74 | rspec-expectations (~> 3.3.0) 75 | rspec-mocks (~> 3.3.0) 76 | rspec-core (3.3.2) 77 | rspec-support (~> 3.3.0) 78 | rspec-expectations (3.3.1) 79 | diff-lcs (>= 1.2.0, < 2.0) 80 | rspec-support (~> 3.3.0) 81 | rspec-mocks (3.3.2) 82 | diff-lcs (>= 1.2.0, < 2.0) 83 | rspec-support (~> 3.3.0) 84 | rspec-support (3.3.0) 85 | slop (3.6.0) 86 | thread_safe (0.3.5) 87 | tzinfo (1.2.2) 88 | thread_safe (~> 0.1) 89 | unf (0.1.4) 90 | unf_ext 91 | unf_ext (0.0.7.1) 92 | virtus (1.0.5) 93 | axiom-types (~> 0.1) 94 | coercible (~> 1.0) 95 | descendants_tracker (~> 0.0, >= 0.0.3) 96 | equalizer (~> 0.0, >= 0.0.9) 97 | 98 | PLATFORMS 99 | ruby 100 | 101 | DEPENDENCIES 102 | airborne 103 | grape 104 | grape-swagger 105 | pry 106 | rspec 107 | 108 | BUNDLED WITH 109 | 1.10.6 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grape API Tutorial 2 | 3 | This repo demonstrates how to build an API with Grape that is documented with Swagger and tested with Rspec/Airborne. 4 | 5 | ## Getting Started 6 | 7 | To set up the project locally: 8 | 9 | 1. Clone the repo. 10 | 2. `cd` into the repo directory on your machine. 11 | 3. run `bundle install` 12 | 13 | Next, to start the API server: 14 | 15 | bundle exec rackup 16 | 17 | Now you can curl the API: 18 | 19 | curl localhost:9292/api/books 20 | 21 | ## Swagger 22 | 23 | To access the Swagger doc that is generated automatically with the `grape-swagger` gem, navigate your browser to 24 | http://localhost:9292/api/swagger_doc. 25 | 26 | ## Taking it further 27 | 28 | When you curl the API's `books` endpoint, you will get an empty array. That's because that endpoint, 29 | defined in `api/books.rb:15`, simply returns an empty array. If you would like to change this behavior, 30 | modify this action. 31 | 32 | If you would like to utilize an ORM like ActiveRecord to access and return `book` records from a database 33 | (you can see an example of this type of behavior on `api/books.rb:10`), you will need to implement this yourself. 34 | See [this section](https://github.com/ruby-grape/grape#activerecord-without-rails) in Grape's README 35 | to add ActiveRecord to this project. You can also check out the `grape-activerecord` gem: 36 | https://github.com/jhollinger/grape-activerecord 37 | 38 | -------------------------------------------------------------------------------- /api/books.rb: -------------------------------------------------------------------------------- 1 | module API 2 | class Books < Grape::API 3 | resource :books do 4 | desc 'Return a book.' 5 | params do 6 | requires :id, type: Integer, desc: 'Book id.' 7 | end 8 | route_param :id do 9 | get do 10 | Book.find(params[:id]) 11 | end 12 | end 13 | 14 | desc 'Return all books.' 15 | get do 16 | [] 17 | end 18 | 19 | desc 'Create a book.' 20 | params do 21 | requires :title, type: String, desc: 'Title.' 22 | requires :description, type: String, desc: 'Description.' 23 | requires :page_count, type: Integer, desc: 'Page count.' 24 | end 25 | post do 26 | Book.create!({ 27 | title: params[:title], 28 | description: params[:description], 29 | page_count: params[:page_count] 30 | }) 31 | end 32 | 33 | desc 'Update a book.' 34 | params do 35 | requires :id, type: Integer, desc: 'Book id.' 36 | requires :title, type: String, desc: 'Title.' 37 | requires :description, type: String, desc: 'Description.' 38 | requires :page_count, type: Integer, desc: 'Page count.' 39 | end 40 | put do 41 | Book.find(params[:id]).update({ 42 | title: params[:title], 43 | description: params[:description], 44 | page_count: params[:page_count] 45 | }) 46 | end 47 | 48 | desc 'Delete a book.' 49 | params do 50 | requires :id, type: Integer, desc: 'Book id.' 51 | end 52 | route_param :id do 53 | delete do 54 | Book.find(params[:id]).destroy 55 | end 56 | end 57 | end 58 | end 59 | end -------------------------------------------------------------------------------- /api/root.rb: -------------------------------------------------------------------------------- 1 | require 'grape' 2 | require 'grape-swagger' 3 | require_relative './books' 4 | 5 | module API 6 | class Root < Grape::API 7 | version 'v1', using: :header, vendor: 'book-company' # this can be path, vendor header, accept-version header, or param 8 | format :json 9 | prefix :api 10 | 11 | mount API::Books 12 | 13 | add_swagger_documentation 14 | end 15 | end -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require './api/root' 2 | run API::Root -------------------------------------------------------------------------------- /spec/api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'airborne' 2 | require_relative '../api/root' 3 | require 'pry' 4 | 5 | Airborne.configure do |config| 6 | config.rack_app = API::Root 7 | end 8 | 9 | describe API::Books do 10 | context 'GET /api/books' do 11 | it 'returns 200 response' do 12 | get "/api/books" 13 | expect_status(200) 14 | end 15 | it 'returns all books' do 16 | get "/api/books" 17 | expect_json([]) 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require './api' 2 | 3 | RSpec.configure do |config| 4 | # rspec-expectations config goes here. You can use an alternate 5 | # assertion/expectation library such as wrong or the stdlib/minitest 6 | # assertions if you prefer. 7 | config.expect_with :rspec do |expectations| 8 | # This option will default to `true` in RSpec 4. It makes the `description` 9 | # and `failure_message` of custom matchers include text for helper methods 10 | # defined using `chain`, e.g.: 11 | # be_bigger_than(2).and_smaller_than(4).description 12 | # # => "be bigger than 2 and smaller than 4" 13 | # ...rather than: 14 | # # => "be bigger than 2" 15 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 16 | end 17 | 18 | # rspec-mocks config goes here. You can use an alternate test double 19 | # library (such as bogus or mocha) by changing the `mock_with` option here. 20 | config.mock_with :rspec do |mocks| 21 | # Prevents you from mocking or stubbing a method that does not exist on 22 | # a real object. This is generally recommended, and will default to 23 | # `true` in RSpec 4. 24 | mocks.verify_partial_doubles = true 25 | end 26 | 27 | # The settings below are suggested to provide a good initial experience 28 | # with RSpec, but feel free to customize to your heart's content. 29 | =begin 30 | # These two settings work together to allow you to limit a spec run 31 | # to individual examples or groups you care about by tagging them with 32 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 33 | # get run. 34 | config.filter_run :focus 35 | config.run_all_when_everything_filtered = true 36 | # Limits the available syntax to the non-monkey patched syntax that is recommended. 37 | # For more details, see: 38 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 39 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 40 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 41 | config.disable_monkey_patching! 42 | # Many RSpec users commonly either run the entire suite or an individual 43 | # file, and it's useful to allow more verbose output when running an 44 | # individual spec file. 45 | if config.files_to_run.one? 46 | # Use the documentation formatter for detailed output, 47 | # unless a formatter has already been configured 48 | # (e.g. via a command-line flag). 49 | config.default_formatter = 'doc' 50 | end 51 | # Print the 10 slowest examples and example groups at the 52 | # end of the spec run, to help surface which specs are running 53 | # particularly slow. 54 | config.profile_examples = 10 55 | # Run specs in random order to surface order dependencies. If you find an 56 | # order dependency and want to debug it, you can fix the order by providing 57 | # the seed, which is printed after each run. 58 | # --seed 1234 59 | config.order = :random 60 | # Seed global randomization in this process using the `--seed` CLI option. 61 | # Setting this allows you to use `--seed` to deterministically reproduce 62 | # test failures related to randomization by passing the same `--seed` value 63 | # as the one that triggered the failure. 64 | Kernel.srand config.seed 65 | =end 66 | end --------------------------------------------------------------------------------