Build APIs with Napa

Share this article

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 KaneDevdatta Kane
View Author

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.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form