APIs are one of the hottest topics in web development. With the increasing number of SPAs (Single Page Applications) and the microservice craze, APIs will become even more popular. As Ruby on Rails developers, we have an amazing framework at our disposal, very capable of building APIs. Though Rails is powerful, it is more complex as well. Sometimes we need a simpler, more nimble framework, one that only does one thing and does it well. I think I have found such a framework. Allow me to introduce you to a framework that is specifically designed for building APIs: Napa.
Napa combines gems like Grape, Roar, and ActiveRecord, allowing us to build powerful APIs with minimal effort. In this tutorial, we will learn how to build an API with token authentication using Napa and Devise.
Installing Napa
Napa is available as a gem, that requires Ruby 2.0. We will need install that first using RVM or rbenv. Switch to Ruby 2.0 and install Napa:
gem install napa --no-ri --no-rdoc
Creating the Project
First, create the project using Napa’s generator:
napa new contact-service -d=pg
By default, Napa will generate a MySQL configured project. The -d=pg
switch to tells Napa to generate a PostgreSQL configured project.
Napa will create a project directory named contact-service and generate all the necessary directories and files. The project is structured as follows:
contact-service
- app
- apis
- models
- representers
- config
- db
- lib
- log
- spec
- Gemfile
- Rakefile
The structure is quite similar to a Rails application, but a little bit simpler. Most of the application’s code goes into the app directory. Our APIs will be in the (wait for it..) apis directory. Models will go into models directory, just like Rails. One of the newer bits here is the representers directory, which will hold the Roar representers. Representers convert the models into the desired JSON output. Other directories serve the same purpose as they do in a Rails app.
Switch to the project directory and install all gems:
cd contact-service
bundle install
After the gems are installed, we will add the database details in .env. First, create the database:
rake db:create
Let’s move on to the next step.
Creating the API
Time for the good stuff, creating the actual API. For that we will first require a model. Create a Contact model with some basic attributes such as name, email and phone number:
napa generate model Contact name:string email:string phone:string
Migrate the database:
rake db:migrate
Ok, now let’s create an API endpoint:
napa generate api contact
Napa will generate the API class – app/apis/contacts_api.rb and the representer class – app/representers/contact_representer.rb. The contacts_api.rb class should look as follows:
class ContactsApi < Grape::API
desc 'Get a list of contacts'
params do
optional :ids, type: Array, desc: 'Array of contact ids'
end
get do
contacts = params[:ids] ? Contact.where(id: params[:ids]) : Contact.all
represent contacts, with: ContactRepresenter
end
desc 'Create a contact'
params do
end
post do
contact = Contact.create!(permitted_params)
represent contact, with: ContactRepresenter
end
params do
requires :id, desc: 'ID of the contact'
end
route_param :id do
desc 'Get an contact'
get do
contact = Contact.find(params[:id])
represent contact, with: ContactRepresenter
end
desc 'Update a contact'
params do
end
put do
# fetch contact record and update attributes. exceptions caught in app.rb
contact = Contact.find(params[:id])
contact.update_attributes!(permitted_params)
represent contact, with: ContactRepresenter
end
end
end
By default, Napa provides a blank declaration of parameters that should be accepted by the API. This is provided for securing the API against attacks. We will update the params
blocks of the post
and put
methods and set the allowed parameters:
...
desc 'Create an contact'
params do
optional :name, type: String, desc: 'The Name of the contact'
optional :phone, type: String, desc: 'The Phone of the contact'
optional :email, type: String, desc: 'The Email Address of the contact'
end
...
...
desc 'Update a contact'
params do
optional :name, type: String, desc: 'The Name of the contact'
optional :phone, type: String, desc: 'The Phone of the contact'
optional :email, type: String, desc: 'The Email Address of the contact'
end
...
Now, we will update the contact_representer.rb class. Add the Contact model fields in the class that should be part of the JSON responses. Update the contact_representer.rb class, as follows:
class ContactRepresenter < Napa::Representer
property :id, type: String
property :name
property :phone
property :email
end
The last remaining part is to add the API to application_api.rb, which mounts the API:
class ApplicationApi < Grape::API
format :json
extend Napa::GrapeExtenders
mount ContactsApi => '/contacts'
add_swagger_documentation
end
Firing Some Requests
Our API is almost ready. Let’s test it using curl
. First, start the development server:
napa server
Now, open another terminal so we can fire requests at the API. First, create a contact:
curl -X POST -d name="Devdatta Kane" -d email="kane.devdatta@gmail.com" -d phone="25451512544" http://localhost:9393/contacts
Napa’s response is always wrapped in a data
element:
{
"data": {
"object_type": "contact",
"id": "1",
"name": "Devdatta Kane",
"email": "kane.devdatta@gmail.com",
"phone": "25451512544"
}
}
Now that we have created a contact, let’s try to fetch all contacts:
curl -X GET http://localhost:9393/contacts
The response will be an array of elements.
{
"data": [
{
"object_type": "contact",
"id": "1",
"name": "Devdatta Kane",
"email": "kane.devdatta@gmail.com",
"phone": "25451512544"
}
]
}
We can fetch a single contact by passing the contact’s ID:
curl -X GET http://localhost:9393/contacts/1
Which responds with:
{
"data": {
"object_type": "contact",
"id": "1",
"name": "Devdatta Kane",
"email": "kane.devdatta@gmail.com",
"phone": "25451512544"
}
}
Try to update the contact’s details:
curl -X PUT -d email="dev@devdatta.com" http://localhost:9393/contacts/1
Napa returns the updated object:
{
"data": {
"object_type": "contact",
"id": "1",
"name": "Devdatta Kane",
"email": "dev@devdatta.com",
"phone": "25451512544"
}
}
Securing the API
We have a fully working API. Yay! But it is completely insecure. BOO! We are allowing API access without any authentication. One of the most simple used authentication method for securing APIs is Token Authentication. We will implement simple token authentication in our API using Devise. We could implement authentication on our own as well, but using a battle tested library such as Devise has its advantages.
First, add Devise in our Gemfile:
gem 'devise'
And update the bundle:
bundle install
Devise is now installed, but needs a config file. In Rails, we can generate the file using a Devise generator, but that won’t work in Napa. So we will manually create the file in the config/initializers directory. Create a file named devise.rb in that directory and add the following:
Devise.setup do |config|
config.secret_key = '6e059f15248c9c26e7208a4a1129029c13f4a0fcef629562c34a4b3f0b1bbcbb8ed1431728cdbc6fe6e6bc4ed0f90cc9fc701c962e63be107e0bfd021eb70f08'
config.mailer_sender = 'a@example.com'
require 'devise/orm/active_record'
config.authentication_keys = [:email]
config.case_insensitive_keys = [:email ]
config.strip_whitespace_keys = [:email ]
config.skip_session_storage = [:http_auth]
config.stretches = Rails.env.test? ? 1 : 10
config.password_length = 8..128
end
Replace the config.secret_key
with a new secret. Now we will create a User model, using Napa’s model generator.
napa generate model User
Napa will create the model class and migration as well. Open the migration class and add the following:
...
def change
create_table :users do |t|
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.string :authentication_token
t.timestamps
end
add_index :users, :email, unique: true
add_index :users, :authentication_token, :unique => true
end
...
Open the app/models/user.rb and add the following:
class User < ActiveRecord::Base
devise :database_authenticatable
before_save :ensure_authentication_token
def ensure_authentication_token
self.authentication_token ||= generate_authentication_token
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
end
Now that we have our model and migration in place, migrate the database:
rake db:migrate
We will create a User using Napa console, which you can fire up with:
napa console
pry(main)> User.create!(email:'kane.devdatta@gmail.com', password:'abcdabcd', password_confirmation:'abcdabcd')
You should get the following output:
=> #<user id: 1, email: "kane.devdatta@gmail.com", encrypted_password: "$2a$10$vdRAL.CusK3IEBTopa1cau5WTLihyNDdssuAtU3otieq...", authentication_token: "yFjgn5cwsFAfKhvU1R_t", created_at: "2015-08-08 11:41:05", updated_at: "2015-08-08 11:41:05">
</user>
There is the user’s authentication_token
which we will need later. Exit the console.
We have to add authentication support to our API. Update the app/apis/application_api.rb as follows:
class ApplicationApi < Grape::API
format :json
extend Napa::GrapeExtenders
before do
error!("401 Unauthorized", 401) unless authenticated?
end
helpers do
def authenticated?
return true if User.find_by_authentication_token(params[:access_token])
end
end
mount ContactsApi => '/contacts'
add_swagger_documentation
end
Now, start napa server
again and test if teh authentication works properly:
curl -X GET http://localhost:9393/contacts
This should result in an error, since we have not passed the access token with the request:
{"error":{"code":"api_error","message":"401 Unauthorized"}}
Try the same request but with access token:
curl -X GET http://localhost:9393/contacts\?access_token\=yFjgn5cwsFAfKhvU1R_t
And you’ll receive a successful response:
{
"data": {
"object_type": "contact",
"id": "1",
"name": "Devdatta Kane",
"email": "kane.devdatta@gmail.com",
"phone": "25451512544"
}
}
Wrap Up
Today we learned how to quickly build APIs with Napa. Napa offers extra functionality through middleware, such as logging and sensitive data scrubbing, which you can explore on Napa’s GitHub repository. Our authentication solution can also be improved using something like OAuth and Access Grants, but I leave that exercise to you.
Comments and feedback welcome, as always.
Frequently Asked Questions (FAQs) about Building APIs with Napa
What is Napa and how does it help in building APIs?
Napa is a framework that is used to build APIs in Ruby. It is designed to be simple, lightweight, and easy to use. It provides a set of tools and conventions that help developers to create APIs that are consistent and well-structured. Napa makes it easy to handle requests and responses, manage database interactions, and perform other common tasks associated with building APIs.
How do I get started with Napa?
To get started with Napa, you first need to install it. You can do this by running the command ‘gem install napa’. Once Napa is installed, you can create a new API by running the command ‘napa new my_api’. This will create a new directory with the necessary files and structure for your API.
How do I define routes in Napa?
In Napa, routes are defined in the ‘routes.rb’ file. You can define a route by specifying the HTTP method (get, post, put, delete), the path, and the controller action that should be executed. For example, ‘get ‘/users’, to: ‘users#index” would route GET requests to ‘/users’ to the ‘index’ action of the ‘UsersController’.
How do I create a controller in Napa?
To create a controller in Napa, you need to create a new file in the ‘app/controllers’ directory. The name of the file should be the pluralized version of the resource name, followed by ‘_controller.rb’. For example, if you are creating a controller for a ‘User’ resource, the file name should be ‘users_controller.rb’. Inside this file, you define a class that inherits from ‘Napa::ControllerBase’ and define your actions.
How do I handle errors in Napa?
Napa provides a built-in mechanism for handling errors. You can define custom error classes that inherit from ‘Napa::Error’. These classes can specify an error code and a message. When an error occurs, you can raise one of these custom errors, and Napa will automatically respond with the appropriate HTTP status code and the error message.
How do I test my Napa API?
Napa supports testing through RSpec. You can write tests for your controllers, models, and other parts of your API. To run your tests, you simply run the command ‘rspec’ in the root directory of your API.
How do I deploy my Napa API?
Napa APIs can be deployed like any other Ruby application. You can use services like Heroku, AWS, or Google Cloud Platform. You simply need to push your code to the service, and it will take care of running your API.
How do I version my Napa API?
Napa supports API versioning through the URL. You can define different versions of your API by creating separate directories under ‘app/controllers’. For example, you could have a ‘v1’ directory for the first version of your API, and a ‘v2’ directory for the second version.
How do I authenticate requests in Napa?
Napa does not provide built-in support for authentication, but you can easily add it using middleware. You can use libraries like ‘rack-auth’ to handle basic and token-based authentication.
How do I handle database interactions in Napa?
Napa uses ActiveRecord for database interactions. You can define models that inherit from ‘Napa::Entity’. These models can then be used to create, read, update, and delete records in your database.
Devdatta Kane is a software developer and designer based in Pune, India. He works with Radinik Technologies building traceability solutions for a variety of industries. He is also the lead developer of refers2, a CRM for small businesses. He works in Ruby on Rails, but likes to dabble with various new technologies as well. An aspiring photographer and passionate traveler, he loves traveling on his motorcycle, capturing experiences through camera.