Blazer JSON API

An extension to Blazer to enable exposing your queries as JSON via API, so that they can be consumed outside of Blazer by your application. Use Blazer for powering in app charts using a charting library of your choice.

Features

  • Powered by SQL Author APIs quickly using Blazers SQL based IDE. Particular useful for private/internal APIs that fall outside your standard API endpoints or where responses need to be taylored to suit a specific charting library.
  • No deploy APIs Experimental APIs can be authored and iterated on quickly via Blazer without the need to do a deploy. Were a team is split between frontend and backend, this greatly increases collaboration and speed.
  • Flexible structure JSON response structure can be controlled directly in SQL by using a column naming convention (double underscore __ denotes a nesting by default, but can be overridden)
  • Security You'll likely want to lock down API access so APIs are authenticated separately to the standard Blazer auth model, so authentication is enabled using HTTP basic authentication to avoid granting everyone with access to Blazer also access to your APIs.
  • URL parameters URL parameters are also supported via Blazers query variables meaning the APIs can be highly dynamic and flexible
  • Pagination Pagination can be controlled using query variables in combination with limits and offsets
  • Multiple data sources Blazer supports multiple data sources meaning you can potentially build APIs that access beyond the applications' database (e.g. ElasticSearch, Google BigQuery, SalesForce etc)
  • Permissions Use Blazers basic permissions mode with your own naming conventions to control access and visibility of API based queries.

Installation

Follow the installation steps described to get Blazer up and running.

Then, add this line to your application's Gemfile:

gem 'blazer_json_api'

And then execute:

$ bundle

And mount the engine in your config/routes.rb:

mount BlazerJsonAPI::Engine, at: 'blazer-api'

Authentication

Don’t forget to protect your Blazer APIs in production.

Basic authentication

Configure authentication in an initializer as follows (e.g. in initializers/blazer_json_api.rb)

BlazerJsonAPI.configure do |config|
  config.username = 'api-username'
  config.password = 'api-password'
end

Devise

Or alternatively, if you use devise, you can conditionally mount the engine using a policy or some user roles.

authenticate :user, ->(user) { user.admin? } do
  mount BlazerJsonAPI::Engine, at: 'blazer-api'
end

Usage

Create queries as normal via Blazer and use the query identifier to render the JSON via the mounted location.

e.g. /blazer-api/queries/1-all-users or /blazer-api/queries/1

URL params can be added where necessary also

e.g. /blazer-api/queries/1-all-users?username=blazer_user

Example queries

A simple index like request

Fetching specifics of all users as follows:

SELECT id, username, first_name, last_name, email, country
FROM users

This would result in the following API response

[
  {
    "id":1,
    "username":"blazer_tommy",
    "first_name":"Tom",
    "last_name":"Carey",
    "email":"[email protected]",
    "country":"Ireland"
  },
  {
    "id":2,
    "username":"blazer_john",
    "first_name":"John",
    "last_name":"Doyle",
    "email":"[email protected]",
    "country":"USA"
  }
]

A simple single resource GET request using a variable

Using a variable, a specific resource can be fetched.

Note: the use of LIMIT 1 can be used to be explicit in desiring a single record in the response, as opposed to a collection

SELECT id, username, first_name, last_name, email, country
FROM users
WHERE username={username}
LIMIT 1

Now, the username can be passed as a URL parameter to the API to fetch the relevant record.

e.g. /blazer-api/queries/1-all-users?username=blazer_john

It would result in the following response.

Using a variable/url parameter conditionally (e.g. optional filter)

It's possible to make a filter optional directly in SQL as follows

SELECT id, username, first_name, last_name, email, country
FROM users
WHERE {username} IS NULL OR username={username}

In this scenario, the url parameter/filter for username is optional.

If provided it will apply the filter, if not provided, it will return an unfiltered response.

Controlling response structure

Standard queries return flat JSON responses that correspond to the results table from executing the SQL.

It's possible to control the JSON structure by using double underscores to denote the desired nesting

Take, for example, the following query:

SELECT users.id, username, first_name, last_name, teams.name as team__name, teams.location as team__location, email
FROM users
JOIN users ON users.team_id = teams.id

Would result in the following structure in the response:

[
  {
    "id":1,
    "username":"blazer_tommy",
    "first_name":"Tom",
    "last_name":"Carey",
    "team":{
      "name":"defenders",
      "location":"Dublin"
    },
    "email":"[email protected]"
  },
  {
    "id":2,
    "username":"blazer_john",
    "first_name":"John",
    "last_name":"Doyle",
    "team":{
      "name":"responders",
      "location":"London"
    },
    "email":"[email protected]"
  }
]

Deeper nesting is also possible, just continue the pattern e.g. a__deeper__nested__value

Paginating potentially large responses

If your query could return a large response, it's generally a good idea to paginate it.

Pagination can be achieved in many ways, but a basic example can be done as follows using a combination of variables in the query and LIMIT and OFFSET.

SELECT id, username, first_name, last_name, email, country
FROM users
LIMIT {per_page}
OFFSET ({page}-1)*{per_page}

Using this technique, URL params can be used by the requester to control pagination.

In this example, page corresponds to the desired page in the paginated collection and per_page corresponds to the desired size of records in each page

This technique can be used in combination with some default settings for these parameters in blazers config file blazer.yml.

Having defaults means if they are not specified by the requester, the defaults will automatically be applied.

    variable_defaults:
      # pagination defaults
      per_page: 30
      page: 1

Requests can then be as follows:

/blazer-api/queries/1-all-users = returns first 30 users (pagination defaults apply automatically)

/blazer-api/queries/1-all-users?page=2 = returns second page containing 30 users

/blazer-api/queries/1-all-users?page=1&per_page=90 = returns first page containing 90 users

etc...

Contributing

Want to improve this library, please do!

  • Report bugs
  • Fix bugs and submit pull requests
  • Write, clarify, or fix documentation
  • Suggest or add new features

License

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