Dash API

Dash API is a Rails engine that mounts an instant REST API for your Ruby on Rails applications using your Postgres database. DashAPI can be queried using a flexible and expressive syntax from URL parameters.

DashAPI is also designed to be performant, scalable and secure.

Note: DashAPI is a pre-release product and not yet recommended for any production applications.

Features

DashAPI is an instant REST API for your Postgres database built using Ruby on Rails. DashAPI supports several features out of the box with little or no configuration required:

  • Full-text search
  • Filtering
  • Sorting
  • Selects
  • Associations
  • Statistics
  • Pagination
  • JWT token authorization
  • Firebase authorization
  • Role-based access control

DashAPI is designed to help rapidly build fully functional, scalable and secure applications by automatically generating REST APIs using any Postgres database. DashAPI also supports advanced features including join associations between tables, full-text keyword search using the native search capabilities of postgres, and

Installation

Add this line to your application's Gemfile:

gem 'dash_api'

And then execute:

$ bundle

Mount the Dash API by updating your routes.rb file:

  mount DashApi::Engine, at: "/dash/api"

Install the pundit policy

rails g pundit:install 

You can also configure DashApi with an initializer by creating a file at config/initializers/dash_api.rb.

# Place file at config/initializers/dash_api.rb

DashApi.tap |config|
  config.jwt_secret = ENV['DASH_JWT_SECRET']
end 

The DashAPI is now ready. You can view all tables at: /dash/api

You can query a table at: /dash/api/<table_name>

Requirements

Dash API requires Ruby on Rails and Postgres, and below are recommended versions:

  • Rails 6+
  • Ruby 2.7.4+
  • Postgres 9+ database

Documentation

Dash supports a flexible, expressive query syntax using URL parameters to query a Postgres database.

All tables can be queried by passing in the table name to the dash API endpoint where you mounted the Dash rails engine:

GET /dash/api/<table>

Example:

GET /dash/api/books 

Schema

Your database schema is available in order to inspect available tables and column data.

GET /dash/api/schema

You can inspect any specific table using the schema endpoint:

GET /dash/api/schema/<table_name>

Example:

GET /dash/api/schema/books

Filtering

You can filter queries using the pattern

GET /dash/api/table?filters=<resource_field>:<operator>:<value>

Example: Below is an example to find all users with ID less than 10:

GET /dash/api/books?filters=id:lt:10 

You can also chain filters to support multiple filters that are ANDed together:

GET /dash/api/books?filters=id:lt:10,published:eq:true 

The currently supported operators are:

eq  - Equals
neq - Not equals
gt  - Greater than
gte - Greater than or equal too
lt  - Less than
lte - Less than or equal too

Sorting

You can sort queries using the pattern:

GET /dash/api/table?order=<field>:<asc|desc>

Example:

GET /dash/api/books?order=title:desc

Pagination

Dash API uses page based pagination. By default results are paginated with 20 results per page. You can paginate results with the following query pattern:

GET /dash/api/table?page=<page>&per_page=<per_page>

Example:

GET /dash/api/books?page=2&per_page=10

Select

Select fields allow you to return only specific fields from a table, similar to the SQL select statement. Select fields follows the query pattern:

GET /dash/api/table?select=<field>,<field>

Example: You can comma separate the fields to include multiple fields in the response

GET /dash/api/books?select=id,title,summary

DashAPI supports native full-text search capabilities. You can search against all fields of a tables with the query syntax:

GET /dash/api/table?keywords=<search_terms>

Example: You can find all users that match the search term below. All keywords are URI decoded prior to issuing a search.

GET /dash/api/books?keywords=ruby+on+rails

Warning: At this time all fields are searchable and using this API which may include any sensitive data in your database.

Associations

Dash takes advantage of Ruby on Rails expressive and powerful ORM, ActiveRecord, to dymacally infer associations between tables and serialize them. Associations currently supported are belongs_to and has_many, and this feature is only available for tables that follow strict Rails naming conventions.

To include a belongs_to table association, use the singular form of the table name:

GET /dash/api/books?includes=author

To include a has_manay table association, use the plural form of the table name:

GET /dash/api/books?includes=reviews 

To combine associations together comma seperate the included tables:

GET /dash/api/books?includes=author,reviews 

Statistics

You can perform calculations for the minimum, maximum, average or count of any field in a table. You may also combine filters or other query parameters to refine results before performing the calculation.

Maxixum

max=<field>

Minimum

min=<field>

Average

avg=<field>

Count

count=<field>

Example:

GET /dash/api/books?avg=ratings 

Create

Create table rows:

POST /dash/api/<table_name>

Body

{
  <table_name>: {
    field: value,    
    ... 
  }
}

Update

Update table rows by ID:

PUT /dash/api/<table_name>/<id>

Body

{
  <table_name>: {
    field: value,    
    ... 
  }
}

Delete

Delete table rows by id:

DELETE /dash/api/<table_name>/<id>

Update many

Bulk update multiple rows by passing in an array of integers the the JSON attributes to update:

POST /dash/api/<table_name>/update_many 

Body

{
  ids: [Integer],
  <table_name>: {
    field: value,    
    ... 
  }
}

Delete many

Bulk delete rows by passing in an array of IDs to delete:

POST /dash/api/<table_name>/delete_many 

Body

{
  ids: [Integer]
}

JWT Token Authorization

The recommended way to secure your API is to use a JWT token. To enable a JWT token, you must first specify the JWT secret key in your configuration at config/initializers/dash_api.rb

Dash API is designed to work alongside an existing API or an additional server which handles authentication. This is accomplished by using a shared JWT secret that is to decode the JWT token.

The JWT decoded object should be a json object and is expected to have a "role" field and a corresponding "id" field to identify the ID of the user.

# /config/initializers/dash_api.rb

DashApi.tap do |config|
  config.jwt_secret = ENV['DASH_JWT_SECRET']  
  ...
end 

Firebase Authorization

DashAPI also supports handling Firebase Authentication. When you sign in with a supported Firebase method, you will receive an idToken which is an encoded JWT token. You may use this token in your requests identically as you would a standard JWT token to authorize requests.

To add Firebase support, add your Firebase Web API Key located under Firebase > Project > Settings to your Dash API configuration.

# /config/initializers/dash_api.rb

DashApi.tap do |config|
  config.firebase_web_key = ENV['FIREBASE_WEB_KEY']  
  ...
end 

Note that since Firebase does not by default assign a role to the JWT payload, the DashAPI will inject the role key with value user to make it easier to manage your access policies. It will also inject a provider key with value firebase to more easily identify firebase requests in your Pundit policies.

For documentation on how to sign in users with Firebase, please check out the official Firebase Authentication documentation.

API Authentication

To authenticate your requests, pass the encoded JWT token in your authorization headers:

Authorization: 'Bearer <JWT_TOKEN>'

You can also pass the token as a url paramter with every request:

/dash/api/...?token=<JWT_TOKEN>

The JWT token will also inspect for the exp key and if present will only allow requests with valid expiration timestamps. For security purposes it's recommended that you encode your JWT tokens with an exp timestamp.

To setup and test JWT tokens, we recommend you explore jwt.io.

Authorization (Pundit)

DashAPI uses the popular Ruby on Rails Pundet gem to manage the authorization policies. Using pundit, you can restrict access to any table or method within a table according to "policies" that you define. These policies map to the User object that is passed from the JWT token.

When you first installed DashAPI, you run the pundit installation generator which creates an ApplicationPolicy file in app/policies. ApplicationPolicy defines all the default policies inherited by all tables in the DashAPI.

The benefit of using Pundit is that you can easily create an acesss policy or search scope by creating a policy for each table in your database that differs from the default policy. To do this, simple create a ruby class that matches the name of the table class followed by the term "Policy." For example, if you want to create a policy for your orders table then you can create a ruby class as follows

# Place this file in /app/policies/order_policy.rb

def OrderPolicy < ApplicationPolicy 
   ...   
end 

You can then specify the polify for each operation from the API. First, below is a sample policy class used by Pundit without any restrictions in place:

class OrderPolicy < ApplicationPolicy 

  def index?
    true 
  end 

  def show? 
    true
  end 

  def update?     
    true 
  end 

  def destroy?
    true 
  end 

  class Scope
    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      scope.all 
    end

    private

    attr_reader :user, :scope
  end
end   

Authorization for scope queries

One common scenario is to restrict access to all Orders that only belong to the current user, unless ther user has role admin any have access to all Orders. You can easily achieve this by specifying the Scope class resolve method:

def resolve 
  if @user.role === 'admin' 
    scope.all 
  else 
    scope.where(user_id: @user.id)
  end
end 

This will restrict all order results to those that only belong to the current user. Again, the user is the JSON payload that is decoded using the JWT token. For example, the token you encode and decode should have at the very least a unique identifier such as id or uid and a role field:

{ id: 1, first_name: 'John', last_name: 'Doe', role: 'admin' }

When this JWT token is passed to Pundit using DashAPI, it will be accessible as @user and you can reference any attribute on user such as @user.id and @user.role within the Pundit policy class.

Authorization for CRUD operations

You can also override ride any specific CRUD operation by specifying the policy for that operation. This will allow you to provide access control that changes for admins and users.

As an example, lets only allow users to be able to delete an order if the order status is a draft order, and not an order that as been paid.

def destroy?
  if @user.role === 'admin'
    true 
  else 
    @user.id === @record.user_id && @record.status === 'draft'
  end 
end 

Pundit provides a simple yet powerful way to manage the access control policies of the DashAPI for any table.

Serialization

DashAPI will serialize all data from the API using the as_json method on the Active Record object. You can exclude sensitive attributes from being serialized during as_json by specifying which attributes to ignore in DashAPi.exclude_attributes using space delimited attribute names.

# /config/initializers/dash_api.rb
DashApi.tap do |config|
  ...
  config.exclude_attributes = ENV['DASH_EXCLUDE_ATTRIBUTES'].split(' ') || []  
  ...
end 

Example:

# /config/initializers/dash_api.rb
DashApi.tap do |config|
  ...
  config.exclude_attributes = "encrypted_password hashed_password"
  ...
end 

You can also exclude tables entirely from the API using the exclude_tables configuration by using space delimited table names:

# /config/initializers/dash_api.rb
DashApi.tap do |config|
  ...
  config.exclude_tables = ENV['DASH_EXCLUDE_TABLES'].split(' ') || []  
  ...
end 

Example:

config.exclude_tables = "api_tokens users private_notes"

Contributing

Contributions are welcome by issuing a pull request at our github repository: https:/github.com/skillhire/dash_api

License

The gem is available as open source under the terms of the MIT License.