MongoDB REST With Ruby on Rails
Some NoSQL databases provide an HTTP REST API to interact with the database. MongoDB, however, has a binary network protocol similar to Postgresql, MySQL, and other databases. In this post, I build a tiny REST API with Ruby on Rails.
Routes
With the objective of supporting only basic CRUD operations, the application needs to handle GET, POST, PATCH, and DELETE at a single end-point. However, if the application should be able to handle anything beyond the basic CRUD operations (Eg. aggregate, update with anything other than $set…), I added the CRUD operator to the URL. The following is my route configuration.
Rails.application.routes.draw do
# Example: http://localhost:3000/mongorest/trains/find?q={"name":"EngineOne"}
get '/mongorest/:collection/find', to: 'mongorest#index'
# Body is the JSON to be inserted
post '/mongorest/:collection/insert', to: 'mongorest#create'
# ?q= is the filter criteria, body is the JSON to be inserted
patch '/mongorest/:collection/update', to: 'mongorest#update'
# ?q= is the filter criteria ; Do a find to look at what will be deleted
delete '/mongorest/:collection/delete', to: 'mongorest#delete'
end
Consider namespacing for the routes.
Controller
With the routes defined, I then build the controller. I hard-coded the database connection (and the DB name “test”), but this should ideally come from a config file. I use the raw MongoDB driver. I used Postman to send the HTTP requests, and I used the Postman generated curl commands within the code comments for trying it without a GUI. The %22 in the URLs is the double-quote character because JSON.parse needs properly-formed JSON syntax (as opposed to JavaScript, in which the keys do not need quotes).
class MongorestController < ApplicationController
before_action :create_mongodb_conn
protect_from_forgery with: :null_session # We dont need CSRF as we are building REST APIs
# http://localhost:3000/mongorest/trains/find?q={"_id":{"$gt":3}}
# curl --location -g 'http://localhost:3000/mongorest/trains/find?q={%22_id%22%3A4}'
def index
find_params = {}
find_params = JSON.parse(params[:q]) unless params[:q].nil?
render json: @client[params[:collection]].find(find_params).to_a.to_json
end
# curl --location 'http://localhost:3000/mongorest/trains/insert' \
# --header 'Content-Type: application/json' \
# --data '{"name": "EngineThree", "_id": 3}'
def create
if request.content_type != 'application/json'
render plain: 'ERROR'
return
end
@client[params[:collection]].insert_one JSON.parse(request.raw_post)
render plain: 'OK'
end
# Only provides $set update
# curl --location -g --request PATCH 'http://localhost:3000/mongorest/trains/update?q={%22_id%22%3A5}' \
# --header 'Content-Type: application/json' \
# --data '{"processed":true}'
def update
find_params = {}
find_params = JSON.parse(params[:q]) unless params[:q].nil?
@client[params[:collection]].update_many find_params, {"$set": JSON.parse(request.raw_post)}
render plain: 'OK'
end
# curl --location -g --request DELETE 'http://localhost:3000/mongorest/trains/delete?q={%22_id%22%3A5}'
def delete
find_params = {}
find_params = JSON.parse(params[:q]) unless params[:q].nil?
@client[params[:collection]].delete_many find_params
render plain: 'OK'
end
private
def create_mongodb_conn
@client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'test')
end
end
Consider returning a status code other than 200 (perhaps 500?) for errors, and adding pagination support. Also, inherit from ApiController instead of ApplicationController.