I development API with Grape gem. I found a style or said pattern which works great and decide to write it down.

There are five steps in an API call: authentication, validation, authorization, operation and presentation. Every step has its own responsibility and we can organize code more structurally. I will use PostAPI to describe each step.

Authentication

Authenticate user before entering endpoint. If user not authenticated, response 401.

Validation

Validate request params. If validation failed, response 400. GET, PATCH, DELETE request need to find the resource first. If not found, response 404.

If Grape default validator is not enough, customize new validator or use form object to do the validation.

Authorization

Check the policy of the resource. For example: maybe only paid user can create post or user can post at most 2 posts every day. If failed, response 403.

Pundit, cancancan are great gems to handle this.

Operation

Create, read, update or delete a resource, any business logic about the resource. Prepare the resource for the next step. If any error happened, response the correspond http status code.

Presentation

Serialize resource. Only logic for transforming format of the resource, such as post.created_at.strftime('%Y/%m/%d').

class PostAPI < Grape::API
  rescue_from BadRequestError do { # 400 }
  rescue_from NotAuthenticatedError do { # 401 }
  rescue_from NotAuthorizedError do { # 403 }
  rescue_from NotFoundError do { # 404 }
  rescue_from StandardError do { # 500 }

  resource 'posts' do
    # authentication
    before { authenticate_user! }

    get ':id' do
      # validation and operation
      post = current_user.posts.find(params[:id])

      # presentation
      present post, with: PostEntity
    end

    # validation
    params do
      requires :title, PostEntity.documentation[:title]
    end
    post do
      # authorization
      authorize current_user, PostPolicy, :create?

      # operation
      post = Post.new(title: params[:title])
      error!('Duplicate post', 409) if post.duplicate?
      post.save

      # presentation
      present post, with: PostEntity
    end
  end
end

class PostPolicy
  def create?
    current_user.paid?
  end
end

class PostEntity < Grape::Entity
  expose :title, documentation: { type: String, allow_blank: false }
  expose :created_at

  def created_at
    object.created_at.strftime('%Y/%m/%d')
  end
end

darren987469