Go back rails routes - API route wi...

rails routes - API route with key in path - model, controller

Rails built in features


API Tokens


class ApiKey < ApplicationRecord
  belongs_to :user
  has_secure_token
end

API subdomain with key


e.g. 
GET api.example.com/v1/xxxxkeyxxx/
GET api.example.com/v1/xxxxkeyxxx/articles
POST api.example.com/v1/xxxxkeyxxx/articles




#config/routes.rb
Rails.application.routes.draw do
  # ...
  namespace :api, path: "", constraints: SubdomainConstraint.new("api"), defaults: { format: 'json' }  do
    namespace :v1 do
      scope "/:key" do
        resources :articles, only: [:index, :create]
        get "/", to: "api_connections#show"
        post "/", to: "api_connections#show"
      end
    end
    match "*path", to: "v1/base#not_found", via: :all
  end
#...

# config/initializers/subdomain_constraint.rb
class SubdomainConstraint
  def initialize(subdomain = nil)
    @subdomain = subdomain
  end

  # With subdomain 'api', this matches api.example.com and api-staging.example.com.
  def matches?(req)
    if @subdomain
      [@subdomain, "#{@subdomain}-staging"].include? req.subdomain
    else
      req.subdomain.present?
    end
  end
end

# app/controllers/api/v1/base_controller.rb
module Api
  module V1
    class BaseController < ActionController::Base
      InvalidApiKey = Class.new(StandardError)

      protect_from_forgery with: :null_session
      respond_to :json

      before_action :authenticate_api_key, except: :not_found
      before_action :set_default_response_format

      rescue_from InvalidApiKey do
        render json: { error: "Invalid API key"} , status: 401
      end

      attr_reader :current_api_key

      def not_found
        render json: { error: "Endpoint not valid"} , status: :not_found
      end

      private

      def set_default_response_format
        request.format = :json
      end

      def authenticate_api_key
        @current_api_key = ApiKey.find_by(key: params[:key]) || raise(InvalidApiKey)
      end
    end
  end
end

one last thing you need middlevare that would transllate all requests to JSON even if content-type header is not present =  ConsiderAllRequestsJsonMiddlevare

require "rails_helper"

RSpec.describe "Api", type: :request do
  let(:headers) { {"HTTP_HOST" => "api.example.com"} } # this will fake the subdomain
  let(:key) { create(:api_key).key }

  it "GET api.example.com/v1/xxxkeyxxx" do
    get "/v1/#{key}", headers: headers
    json = JSON.parse(response.body)
    expect(response).to be_successful
    #...
  end

#...

it "valid payload should create an article" do
  params = { fullname: "Viktoria Nikolova" }

  expect { post "/v1/#{api_connection.key}/articles", params: params.to_json, headers: headers }
    .to change { Article.count }.by(1)

  expect(response).to be_successful
  json = JSON.parse(response.body)
  expect(json).to match({"result" => "ok"})
end