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:
- Jutsu 15 - Building a blogging web API with Grape, MongoDB and the JSON API specification
- Jutsu 16 - Understanding the JSON API specification (JSON API)
- Jutsu 17 - Creating the Yumi library, a JSON API implementation, from scratch (Ruby, JSON API)
- Jutsu 18 - Integrating Yumi, our JSON API library, in Bloggy (Grape, JSON API)
- Jutsu 19 - Enforcing the media type and improving the controllers of Bloggy (Grape, JSON API)
- Jutsu 20 - Bloggy: How to test a Grape web API (Grape, JSON API, Rspec, Factory Girl)
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
andCOMMENT_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
andCOMMENT_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