โ† Back to Blog
Jutsu6/8/2025

Bloggy #1 - Building a blogging web API with Grape, MongoDB and the JSON API specification

bloggrapejsonapimongodbruby
Bloggy #1 - Building a blogging web API with Grape, MongoDB and the JSON API specification

In this fifteenth jutsu, we're going to do something special. This is going to be the first article of a series that will teach you how to build a blog from scratch using Grape, MongoDB, Ember.js and a JSON format: JSON API.

This first part will be about creating a basic Grape API with MongoDB backing it up. Later on, this API will become an hypermedia API.

Here is the complete list of articles in this series:

The next series of jutsus will focus on creating an Ember.js application from scratch to go with this web API.

It's going to be an interesting journey and I hope you will learn a lot on the way.

Let's get started!

Master Ruby Web APIs [advertisement]

I'm currently writing a new book titled Master Ruby Web APIs that will teach you, you guessed it, how to build web APIs using the awesome Ruby language.

To give you an idea, the book will cover:

  • The Web, Standards and HTTP
  • Custom APIs, Hypermedia APIs, RESTful APIs and more
  • Pure Ruby, Sinatra and Rails 5
  • And a lot more!

Take a look and register to get awesome benefits when the book is released.

Tools

We'll be using a set of awesome tools to build this blogging system. I hope you will learn a bit along the way ;)

Grape

Grape is an "opinionated micro-framework for creating REST-like APIs in Ruby". We will use it to build the endpoints of our web API and send back the JSON data to the client.

MongoDB

MongoDB is a NoSQL document database. It will be used to store the data of our blog.

JSON API

JSON API is a specification for building APIs in JSON. I want to show you how to stop making custom JSON every time you build a new API and start following one of the universal specifications. This will be very useful when we create the Ember.js application since we will be able to use an existing adapter to receive the data.

Ember.js

Ember.js is a frontend Javascript framework to 'create ambitious web applications'. We will use it to create the frontend of our blog. In another jutsu, we will see how to make it SEO-friendly.

The Project

So we're going to use this set of technologies to build a simple yet awesome blogging engine from scratch. There won't be a user login system at first but we will add it in a future jutsu. For now we'll focus on having the following in our API: posts, comments and tags.

Let's see how we're going to define those entities and the endpoints that will let us access them.

Database Collections

Since we are using a NoSQL database, we will be focusing on documents and embedded documents and avoid relationships that are not necessary. We actually only need one document in which we will embed both tags and comments. Here is an example of how one post will look like:

{
  "_id": ObjectId("lkjf02i9i393j93309430249"),
  "slug": "my-awesome-post",
  "title": "My Awesome Post",
  "content": "This is a super amazing post with a lot of content",
  "tags": [
    {
      "name": "Awesome",
      "slug": "awesome"
    },
    {
      "name": "Super Cool",
      "slug": "super-cool"
    }
  ],
  "comments": [
    "author": "Tibo",
    "email": "mail@example.com",
    "website": "example.com",
    "content": "This is just a comment!"
  ]
}

We basically embed the list of tags and the list of comments inside a post. Like this, each post is isolated and retrieving the data is super simple.

Endpoints

For our API, we need two sets of endpoints: the admin part and the client part. In the admin part, we should be able to create, write, update and delete posts. In the client side, we only need to retrieve posts (many or just one) and create/update/delete comments.

Admin

I'm going to list the admin endpoints here. Those endpoints will be used through an admin interface in the future to manage blog posts.

Posts

Here are the admin routes for the Post entity.

GET /admin/posts: List of posts (index)

GET /admin/posts/1: Get one post (show)

POST /admin/posts: Create a post (create)

PATCH /admin/posts/1: Update a post (update)

DELETE /admin/posts/1: Delete a post (destroy)

Client

Now let's see the endpoints we need to present to the public part of our blog. People need to be able to get the list of posts, get a specific post and comment stuff.

Posts

Here are the client routes for the Post entity.

GET /posts: List of posts (index)

GET /posts/1: Get one post (show)

Comments

Here are the client routes for the Comment entity.

POST /posts/1/comments: Create a comment (create)

PATCH /posts/1/comments/1: Update a comment (update)

DELETE /posts/1/comments/1: Delete a comment (destroy)

That's all we need for our little blogging web API. How about we get started and build it? :)

Versioning

Our API will be versioned by adding a prefix in the URL. I feel that it's much easier for people to understand that way in a tutorial. We will only make a v1 for our blogging API as you will see in the next steps.

Build it!

Time to get our hands dirty. We're first going to setup the application and the dependencies we need. Then we will create our models (Post, Comment, Tag) and finally add all the endpoints we need, both for the admin part and the public part.

1. Create the application

Let's create our application. We are not creating a Rails application so we cannot just use some kind of generator. Plus I want to show you how to do it from scratch.

Let's create a new folder, move inside and create a Gemfile. We will then use this Gemfile to define what we need and use the awesome Bundler to install everything.

mkdir grape-blog && cd grape-blog
touch Gemfile

For now, we just need the grape gem. Here's the content for the Gemfile file:

# Gemfile
source 'https://rubygems.org'

gem 'grape'

Now a quick bundle install will give us what we need to start coding.

Our application is going to look a bit like a Rails application with an app/ folder containing models/ and api/. It will basically look like this:

app/
  api/
    v1/
      admin/
  models/
  config/
  application.rb
  config.ru
  Gemfile
  Gemfile.lock

We need an entry point for our application. For this, we're going to create a file named application.rb where we will require stuff, define the base Grape API object and tell Rack how to mount our application.

This file is located at the root of our application folder.

Here is the code:

# application.rb
require 'grape'

# Load files from the models and api folders
Dir["#{File.dirname(__FILE__)}/app/models/**/*.rb"].each { |f| require f }
Dir["#{File.dirname(__FILE__)}/app/api/**/*.rb"].each { |f| require f }

# Grape API class. We will inherit from it in our future controllers.
module API
  class Root < Grape::API
    format :json
    prefix :api

    # Simple endpoint to get the current status of our API.
    get :status do
      { status: 'ok' }
    end

  end
end

# Mounting the Grape application
SamurailsBlog = Rack::Builder.new {

  map "/" do
    run API::Root
  end

}

Finally, we need to create a file named config.ru so Rack will know how to start the application.

This file is located at the root of our application folder.

# config.ru
require File.expand_path('../application', __FILE__)

run SamurailsBlog

Yay! We are ready to run our server for the first time. Run the server with rackup and access http://localhost:9292/api/status.

You should see this beautiful json:

{"status":"ok"}

2. Setup the dependencies

Let's add some persistence to our application. As you know, we will be storing posts, tags and comments. First, we need to install Mongodb and the cool mongoid gem that will be our ORM.

If you are using a Mac, just use Homebrew to install Mongodb:

brew install mongodb

If not, checkout those links to install it (you should really use Homebrew on OS X though):

Once you have Mongodb, we need to install mongoid. To do so, we can just add it to the Gemfile.

# Gemfile
source 'https://rubygems.org'

gem 'grape'
gem 'mongoid', '~> 5.0.0'

Then a simple bundle install should put everything in place.

Now that mongoid is installed and loaded, we need to define the configuration. Basically, we need to tell mongoid where to find the database.

Let's create the file config/mongoid.config and add the following configuration inside.

development:
  clients:
    default:
      database: samurails_blog
      hosts:
        - localhost:27017

We need to add a line to the application.rb file to require the mongoid gem and load our configuration.

require 'grape'
require 'mongoid'

Mongoid.load! "config/mongoid.config"

# ... More Stuff...

Alright, we're finally done setting up stuff. It's time to create our models!

3. Create the models (M)

Let's add our models now! Since we're using mongoid, we need to define the fields of our documents inside the model.

Here is the code of our 3 models with comments.

Post

# app/models/post.rb
class Post
  # We define this class as a Mongoid Document
  include Mongoid::Document

  # Generates created_at and updated_at
  include Mongoid::Timestamps

  # Defining our fields with their types
  field :slug, type: String
  field :title, type: String
  field :content, type: String

  # tags and comments will be stored inside the
  # Post document
  embeds_many :tags
  embeds_many :comments

  # Sort the posts
  scope :ordered, -> { order('created_at DESC') }

  # Validates that the slug is present and unique
  validates :slug, presence: true, uniqueness: true
  validates :title, presence: true

  # The slug has to be unique since it can be used to query post.
  # Also defining an index will make the query more efficient
  index({ slug: 1 }, { unique: true, name: "slug_index" })
end

Tag

# app/models/tag.rb
class Tag
  include Mongoid::Document
  include Mongoid::Timestamps

  field :slug, type: String
  field :name, type: String

  # This model should be saved in the post document
  embedded_in :post

  validates :slug, presence: true, uniqueness: true
  validates :name, presence: true

  index({ slug: 1 }, { unique: true, name: "slug_index" })
end

Comment

# app/models/comment.rb
class Comment
  include Mongoid::Document
  include Mongoid::Timestamps

  field :author, type: String
  field :email, type: String
  field :website, type: String
  field :content, type: String

  # This model should be saved in the post document
  embedded_in :post

  validates :author, presence: true
  validates :content, presence: true
end

That's it, we have all the models we need!

If you'd like, you can use the racksh command to run something like rails console for our Rack application. You will be able to test the models by creating, updating and deleting them.

4. Adding the client endpoints (C)

Models are ready, we can implement our API endpoints. We will be storing our controllers in app/api/v1/. We won't follow the Rails conventions of adding _controller at the end of every controller file. We will just use the plural version of the models: posts.rb, etc.

I know it's preferred to use headers to store the API version but since this is a tutorial and we will be using cURL, I prefer to keep it simple and easy to understand.

Posts

If you remember, we wanted the following routes for the Post entity:

GET /posts: List of posts (index)

GET /posts/1: Get one post (show)

Well, let's create them with Grape! We need to create a file named app/api/v1/posts.rb with the following content. This is the simplest controller we will create which is why we're starting with it.

# app/api/v1/posts.rb
module API
  module V1
    class Posts < Grape::API
      version 'v1', using: :path, vendor: 'samurails-blog'

      resources :posts do

        desc 'Returns all posts'
        get do
          Post.all.ordered
        end

        desc "Return a specific post"
        params do
          requires :id, type: String
        end
        get ':id' do
          Post.find(params[:id])
        end

      end
    end
  end
end

That's all we need from the posts controller serving the list of posts to the client. For now, we're letting Grape convert our objects to JSON. We will come back to this in the next Jutsu and implement the JSON API specification instead.

Comments

The comments controller is a bit more complicated. If you don't remember, here is the list of endpoints we need to implement in this controller:

POST /posts/1/comments: Create a comment (create)

PATCH /posts/1/comments/1: Update a comment (update)

DELETE /posts/1/comments/1: Delete a comment (destroy)

Let's add it now. Create the file app/api/v1/comments.rb with the following content:

# app/api/v1/comments.rb
module API
  module V1
    class Comments < Grape::API
      version 'v1', using: :path, vendor: 'samurails-blog'

      # Nested resource so we need to add the post namespace
      namespace 'posts/:post_id' do
        resources :comments do

          desc 'Create a comment.'
          params do
            requires :author, type: String
            requires :email, type: String
            requires :website, type: String
            requires :content, type: String
          end
          post do
            post = Post.find(params[:post_id])
            post.comments.create!({
              author: params[:author],
              email: params[:email],
              website: params[:website],
              content: params[:content]
            })
          end

          desc 'Update a comment.'
          params do
            requires :id, type: String
            requires :author, type: String
            requires :email, type: String
            requires :website, type: String
            requires :content, type: String
          end
          put ':id' do
            post = Post.find(params[:post_id])
            post.comments.find(params[:id]).update!({
              author: params[:author],
              email: params[:email],
              website: params[:website],
              content: params[:content]
            })
          end

          desc 'Delete a comment.'
          params do
            requires :id, type: String, desc: 'Status ID.'
          end
          delete ':id' do
            post = Post.find(params[:post_id])
            post.comments.find(params[:id]).destroy
          end

        end
      end

    end
  end
end
  • Create: checked
  • Update: checked
  • Delete: checked

Cool, this controller is doing everything we need from it!

5. Create the Admin endpoints (C)

We are only going to create one controller for the admin part. That means we won't have any comment moderation or anything like that... Come on, I cannot fit everything in this article! I will come back to it and create a new Jutsu if you guys are interested.

For now, let's add our admin controller: app/api/v1/admin/posts.rb

Quick reminder of the list of endpoints that we need for this admin controller:

  • GET /admin/posts: List of posts (index)
  • GET /admin/posts/1: Get one post (show)
  • POST /admin/posts: Create a post (create)
  • PATCH /admin/posts/1: Update a post (update)
  • DELETE /admin/posts/1: Delete a post (destroy)

And here is the content of the controller:

# app/api/v1/admin/posts.rb
module API
  module V1
    module Admin
      class Posts < Grape::API
        version 'v1', using: :path, vendor: 'samurails-blog'

        namespace :admin do

          resources :posts do

            desc 'Returns all posts'
            get do
              Post.all.ordered
            end

            desc "Return a specific post"
            params do
              requires :id, type: String
            end
            get ':id' do
              Post.find(params[:id])
            end

            desc "Create a new post"
            params do
              requires :slug, type: String
              requires :title, type: String
              requires :content, type: String
            end
            post do
              Post.create!(slug: params[:slug],
                                   title: params[:title],
                                   content: params[:content])
            end

            desc "Update a post"
            params do
              requires :id, type: String
              requires :slug, type: String
              requires :title, type: String
              requires :content, type: String
            end
            put ':id' do
              post = Post.find(params[:id])
              post.update(slug: params[:slug],
                          title: params[:title],
                          content: params[:content])
            end

            desc "Delete a post"
            params do
              requires :id, type: String
            end
            delete ':id' do
              Post.find(params[:id]).destroy
            end

          end
        end
      end
    end
  end
end

6. Mounting the controllers

Last step before we can test our endpoints, we need to mount our controllers inside the application.rb file.

# application.rb
# Require, Config loading, ...

module API
  class Root < Grape::API
    format :json
    prefix :api

    get :status do
      { status: 'ok' }
    end

    mount V1::Admin::Posts
    mount V1::Comments
    mount V1::Posts
  end
end

# Rack Builder, ...

Aaaaand, we're done!

Final Test

Now start up your server and use the following cURL requests to check that everything is working correctly. In the Jutsu 17, after implementing the JSON API spec (in Jutsu 16), we will write automated tests for this web API.

Admin Posts controller cURL Requests

  • List of posts
curl http://localhost:9292/api/v1/admin/posts
  • Get a specific post (replace the POST_ID part)
curl http://localhost:9292/api/v1/admin/posts/POST_ID
  • Create a post
curl http://localhost:9292/api/v1/admin/posts -d "slug=superslug&title=Test1&content=this is my post content"
  • Update a post (replace the POST_ID part)
curl -X PUT http://localhost:9292/api/v1/admin/posts/POST_ID -d "slug=superslugx&title=Test2&content=this is my post content"
  • Delete a post (replace the POST_ID part)
curl -X DELETE http://localhost:9292/api/v1/admin/posts/POST_ID

Client Posts controller cURL Requests

  • Get the list of posts
curl http://localhost:9292/api/v1/posts
  • Get a specific post (replace the POST_ID part)
curl http://localhost:9292/api/v1/posts/POST_ID

Client Comments controller cURL Requests

  • Create a comment (change the POST_ID)
curl http://localhost:9292/api/v1/posts/POST_ID/comments -d "author=thibault&email=thibault@example.com&website=samurails.com&content=Cool"
  • Update a comment (change the POST_ID and COMMENT_ID)
curl -X PUT http://localhost:9292/api/v1/posts/POST_ID/comments/COMMENT_ID -d "author=tibo&email=tibo@example.com&website=example.com&content=Awesome"
  • Delete a comment (change the POST_ID and COMMENT_ID)
curl -X DELETE http://localhost:9292/api/v1/posts/POST_ID/comments/COMMENT_ID

Source Code

The code is available on GitHub.

The End (for now)

That's it for this jutsu. I hope you learned how to create Grape web API by building this small blogging API. We will come back to it in the next jutsus: it's going to be a lot of fun!

Next up: Jutsu #16 โ€“ Bloggy: Understanding the JSON API specification

Comments

Loading comments...

Level Up Your Dev Skills & Income ๐Ÿ’ฐ๐Ÿ’ป

Learn how to sharpen your programming skills, monetize your expertise, and build a future-proof career โ€” through freelancing, SaaS, digital products, or high-paying jobs.

Join 3,000+ developers learning how to earn more, improve their skills, and future-proof their careers.

Bloggy #1 - Building a blogging web API with Grape, MongoDB and the JSON API specification | Devmystify