A tutorial on how to built a headless CMS with Ruby on Rails
Table of Contents
Introduction
1.1 Overview of Book Bazaar
1.2 The Role of Headless CMS in Modern Development
1.3 Why Ruby on Rails?
1.4 Relevance to ICT Trends and Professional ProfilesInstallation
2.1 Prerequisites
2.2 Setting Up the Project
2.3 Configuring Docker (Optional)Configuration
3.1 Database Configuration
3.2 Adding Gems to Enhance Functionality
3.3 Setting Up Application StructureBasic Functionality
4.1 Creating Models and Migrations
4.2 Setting Up Controllers and Routes
4.3 Building API Endpoints
4.4 Testing APIs with Postman andcurl
Advanced Functionalities
5.1 Implementing Pagination with Kaminari
5.2 Adding SEO with FriendlyId
5.3 Filtering and Sorting Products
5.4 Tracking Popular ItemsImages, Diagrams, and Code Snippets
6.1 Screenshots of the Setup Process
6.2 Entity-Relationship Diagram (ERD)
6.3 API Request-Response Lifecycle DiagramBest Practices
7.1 Following Rails Conventions
7.2 Securing APIs with JWT
7.3 Validating Inputs and Preventing Invalid Data
7.4 Optimizing QueriesFuture Updates and Versions
8.1 Features to Expand Book Bazaar
8.2 New Features in Upcoming Rails Versions
Introduction
Overview of Book Bazaar
Book Bazaar is a simplified headless CMS, inspired by platforms like Shopify, that focuses on flexibility and scalability. It allows developers to manage and serve content independently of the frontend, making it ideal for modern applications that require API-first architecture. The backend is built with Ruby on Rails, leveraging its robust framework to deliver clean, maintainable, and performant code. By the end of this tutorial, you’ll have a functional CMS backend capable of handling products, categories, and orders, with advanced features like pagination, filtering, and SEO.
The Role of Headless CMS in Modern Development
Traditional CMS platforms often tie the backend directly to the frontend, limiting flexibility in delivering content to multiple platforms. A headless CMS decouples the two, allowing the backend to serve data via APIs that can be consumed by websites, mobile apps, or even IoT devices. This approach is becoming increasingly popular due to its ability to provide omnichannel experiences, faster load times, and developer-friendly APIs. Book Bazaar embraces this modern approach, enabling seamless integration with diverse frontends while ensuring scalability and performance.
Why Ruby on Rails?
Ruby on Rails is a proven web development framework known for its productivity and convention-over-configuration philosophy. It includes powerful tools for database management, routing, and API creation out of the box, which makes it a natural choice for building a headless CMS like Book Bazaar. Features such as Active Record, RESTful architecture, and the availability of a rich ecosystem of gems allow developers to rapidly prototype and deploy applications. Rails also emphasizes clean, maintainable code, which is essential for long-term project sustainability.
Relevance to ICT Trends and Professional Profiles
Headless CMS solutions align with current ICT trends such as API-first development, cloud-based services, and modular application design. They cater to the growing need for scalable and versatile content delivery systems. For developers, mastering this approach expands opportunities across various career profiles:
- Full-Stack JavaScript Developers can create dynamic frontends to consume the APIs.
- Front-End Developers can focus solely on the UI/UX without backend constraints.
- Back-End Developers can explore efficient API design and architecture.
- CMS Themers can integrate Book Bazaar with various platforms to provide customized solutions.
By building Book Bazaar, you gain hands-on experience in addressing real-world challenges, keeping you competitive in the rapidly evolving tech landscape.
Installation
Prerequisites
Before starting, ensure that your development environment is properly set up. Below are the tools and software you will need:
- Ruby: Install the latest version of Ruby. You can check if Ruby is installed by running:bash
ruby -v
If not installed, download it from (https://www.ruby-lang.org/en/downloads/).
Ruby on Rails: Install Rails using the following command:
bashgem install rails
Confirm the installation by running:
bashrails -v
MySQL: Book Bazaar uses MySQL as the database. Install MySQL by following the instructions on the official website: https://dev.mysql.com/doc/.
VSCode: Use VSCode for coding. Download it from https://code.visualstudio.com/.
Optional: Docker: For containerizing the database, install Docker. Download it from https://www.docker.com/.
Setting Up the Project
Create a New Rails Application: Run the following command in your integrated terminal to create a new Rails application named
book_bazaar
:bashrails new book_bazaar --api -d mysql
Navigate to the Project Directory: Move into the newly created project directory:
bashcd book_bazaar
Start the Rails Server: Launch the Rails server to ensure the application is running correctly:
bashrails s
Access the Application: Open your browser and visit
http://localhost:3000
. You should see the Rails welcome page, indicating that the setup was successful.Stop the Server: Press
Ctrl + C
in the terminal to stop the Rails server.
Configuring Docker (Optional)
If you prefer to use Docker for database management, follow these steps:
Create a
docker-compose.yml
File: Create a new file nameddocker-compose.yml
in the project root directory and add the following configuration:yamlversion: '3.8' services: db: image: mysql:5.7 restart: always environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: book_bazaar_development MYSQL_USER: root MYSQL_PASSWORD: password ports: - "3306:3306"
Start the Docker Container: Run the following command to start the MySQL container:
bashdocker-compose up -d
Update config/database.yml: Modify the
config/database.yml
file to use the Docker container for the database connection. Update thedevelopment
section as follows:yamldevelopment: adapter: mysql2 encoding: utf8 database: book_bazaar_development pool: 5 username: root password: password host: db
Create the Database: Run the following command to create the database:
bashrails db:create
Testing setup: Verify the rails server is running by starting it with
rails s
and accessinghttp://localhost:3000
in your browser. If using Docker, ensure the MySQL container is running by checkingdocker ps
in your terminal.Test database connection: Confirm the database connection by running
rails db:migrat:status
to create the necessary tables.
Configuration
3.1 Database Configuration
To connect your Rails application to a MySQL database, update the config/database.yml
file. Replace the default settings with your MySQL database credentials:
default: &default
adapter: mysql2
encoding: utf8
pool: 5
username: your_database_username
password: your_database_password
host: localhost
development:
<<: *default
database: book_bazaar_development
test:
<<: *default
database: book_bazaar_test
production:
<<: *default
database: book_bazaar_production
username: <%= ENV['DATABASE_USERNAME'] %>
password: <%= ENV['DATABASE_PASSWORD'] %>
Save the file and create the database by running rails db:create
. This will create book_bazaar_development, book_bazaar_test, and book_bazaar_production databases.
3.2 Adding Gems to Enhance Functionality
Ruby on Rails has a vibrant ecosystem of gems that can add powerful features to your application. In this tutorial, we’ll use the following gems:
- Kaminari: For pagination
- FriendlyId: For SEO-friendly URLs Add these gems to your Gemfile:
gem 'kaminari'
gem 'friendly_id'
After adding the gems, install them by running:
bundle install
Once installed, configure the gems as needed:
- Generate the Kaminari views (optional):bash
rails g kaminari:views bootstrap4
- Generate a migration for FriendlyId:bash
rails generate friendly_id rials db:migrate
3.3 Setting Up Application Structure
To organize the application, create the necessary models, controllers, and routes. Here’s a basic structure to get you started:
- Models: Create models for products, categories, and orders. Use the following commands to generate the models:bash
rails g model Product name:string price:decimal category:references rails g model Category name:string rails g model Order total:decimal
- Controllers: Generate controllers for products, categories, and orders:bash
rails g controller Products rails g controller Categories rails g controller Orders
- Routes: Define routes for the API endpoints in
config/routes.rb
:rubynamespace :api do namespace :v1 do resources :products resources :categories resources :orders end end
- Migrations: Run the migrations to create the necessary tables in the database:bash
rails db:migrate
- Seed Data: Optionally, add seed data to populate the database with sample records:ruby
# db/seeds.rb Category.create(name: 'Fiction') Category.create(name: 'Non-Fiction') Product.create(name: 'Book 1', price: 10.99, category_id: 1) Product.create(name: 'Book 2', price: 15.99, category_id: 2) Order.create(total: 26.98)
Run rails db:seed
to populate the database with the seed data.
Basic Functionality
4.1 Creating Models and Migrations
In this section, we’ll create the foundational models for our Book Bazaar CMS. We need models for the main entities in the system, such as Product
, Category
, and Order
. First, we’ll define the database schema by creating migrations for these models.
To create a Product
model, use the following command:
rails g model Product name:string description:text price:decimal stock:integer
rails db:migrate
Next, generate a Category
model:
rails g model Category name:string
rails db:migrate
Finally, create an Order
model:
rails g model Order total:decimal
rails db:migrate
4.2 Setting Up Controllers and Routes
Once the models are in place, we need to create controllers to handle incoming API requests and perform actions on the models. Rails makes this process simple with its scaffold generators.
Generate a scaffold for the Product
controller:
rails g controller Api::Products
This will generate the controller with basic CRUD actions (index, show, create, update, destroy). Open the generated controller in app/controllers/api/products_controller.rb and implement these actions:
class Api::ProductsController < ApplicationController
def index
products = Product.all
render json: products
end
def show
product = Product.find(params[:id])
render json: product
end
def create
product = Product.new(product_params)
if product.save
render json: product, status: :created
else
render json: product.errors, status: :unprocessable_entity
end
end
def update
product = Product.find(params[:id])
if product.update(product_params)
render json: product
else
render json: product.errors, status: :unprocessable_entity
end
end
def destroy
product = Product.find(params[:id])
product.destroy
head :no_content
end
private
def product_params
params.require(:product).permit(:name, :description, :price, :stock)
end
end
Repeat the same process for the Category
and Order
controllers. Define the necessary actions to handle CRUD operations on these resources. Now, update the config/routes.rb file to create API routes for products:
namespace :api do
namespace :v1 do
resources :products
resources :categories
resources :orders
end
end
This defines the necessary routes for interacting with the Product resource via the API.
4.3 Building API Endpoints
With the models and controllers set up, we now have to ensure our API endpoints are working correctly. Using Rails, the index action will return all products, the show action will return a single product, and so on.
To test the API endpoints, start the Rails server by running rails s
and use a tool like Postman to send requests to the API. For example, to get all products, send a GET request to http://localhost:3000/api/v1/products
.
curl http://localhost:3000/api/v1/products
You should receive a JSON response containing all the products in the database.
curl http://localhost:3000/api/products/1
This will return the details of the product with ID 1.
4.4 Testing APIs with Postman and curl
For thorough testing, Postman is an excellent tool for sending requests to your API and inspecting responses. You can use it to test all CRUD operations for products, including creating, updating, and deleting products.
Alternatively, you can use curl
commands in the terminal to interact with the API. For example, to create a new product, you can send a POST request using curl
:
curl -X POST -H "Content-Type: application/json" -d '{"name": "Book 3", "price": 19.99, "stock": 100}' http://localhost:3000/api/v1/products
This will create a new product with the specified details. Using curl for testing is straightforward:
-X
specifies the HTTP method (GET, POST, PUT, DELETE).-H
sets the request header.-d
sends data in the request body.
To update a product:
curl -X PUT -H "Content-Type: application/json" -d '{"price": 24.99}' http://localhost:3000/api/v1/products/1
To delete a product:
curl -X DELETE http://localhost:3000/api/v1/products/1
By testing these API endpoints, you can ensure that your Book Bazaar CMS is functioning as expected.
Advanced Functionalities
5.1 Implementing Pagination with Kaminari
Pagination is an essential feature for handling large datasets efficiently. In this section, we'll integrate the Kaminari gem to paginate the product listings in our API. This allows users to view products in chunks, improving performance and user experience.
To get started, first install the Kaminari gem by adding it to your Gemfile
:
gem 'kaminari'
Run bundle install to install the gem. Then, modify the controller to paginate the products:
class Api::ProductsController < ApplicationController
def index
products = Product.page(params[:page]).per(params[:per_page])
render json: products
end
end
This will return a paginated list of products, 10 per page. You can adjust the number per page based on your needs. To access different pages, provide a page query parameter, like /api/products?page=2.
5.2 Adding SEO with FriendlyId
SEO optimization is key for improving the visibility of your content. In this section, we’ll use the FriendlyId gem to generate human-readable slugs for our products, enhancing their SEO-friendliness.
Start by adding the gem to your Gemfile:
gem 'friendly_id'
Run bundle install to install the gem. Next, generate a migration to add a slug column to the products table:
rails generate migration add_slug_to_products slug:string:uniq
rails db:migrate
Update the Product model to include the FriendlyId module:
class Product < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
end
This configuration generates a URL-friendly slug based on the product name, allowing for cleaner, more SEO-friendly URLs like /products/awesome-book.
5.3 Filtering and Sorting Products
Providing users with filtering and sorting options is crucial for improving the user experience, especially in large product catalogs. In this section, we’ll implement basic filtering by category and price range, as well as sorting by popularity.
To filter products by category, update the index action in the Products controller:
class Api::ProductsController < ApplicationController
def index
products = Product.all
products = products.where(category_id: params[:category_id]) if params[:category_id].present?
products = products.where('price >= ?', params[:min_price]) if params[:min_price].present?
products = products.where('price <= ?', params[:max_price]) if params[:max_price].present?
render json: products
end
end
This allows users to filter products by category ID and price range. To sort products by popularity, you can track the number of views for each product and order the products based on this metric.
5.4 Tracking Popular Items
To keep track of product popularity, we can implement a basic view counter. This feature will allow us to increment the view count every time a product is accessed, which can be useful for highlighting popular items. First, add a views column to the products table:
rails generate migration add_views_to_products views:integer
rails db:migrate
Next, modify the show action to increment the view count:
class Api::ProductsController < ApplicationController
def show
product = Product.find(params[:id])
product.increment!(:views)
render json: product
end
end
This will increment the view count every time a product is accessed. You can then sort products based on the view count to display popular items.
Images, Diagrams, and Code Snippets
6.1 Screenshots of the Setup Process
6.2 Entity-Relationship Diagram (ERD)
Example ERD:
+–––––––+ +——————+ +————+ | Product | | Category | | Order | +–––––––+ +——————+ +————+ | id | | id | | id | | name | | name | | product_id | | description | | description | | quantity | | price | +——————+ | total_price| | stock | | created_at | +–––––––+ +————+ | | +—————————————————–+
Foreign Key: product_id: references Product(id)
6.3 API Request-Response Lifecycle Diagram
API Request-Response Lifecycle: Client Request -> API Controller -> Model/Database Query -> Response -> Client.
Best Practices
7.1 Following Rails Conventions
Conventions over Configuration: Rails is built around a philosophy of "convention over configuration." Following Rails conventions ensures that your application stays aligned with the expectations of the Rails community. For example, the names of your controllers, models, and views should follow specific conventions to reduce the need for additional configuration. Adhering to this principle will make your code easier for others to read, and will allow Rails to handle common tasks, such as URL routing and file management, automatically.
RESTful Routes: REST (Representational State Transfer) is an architectural style that ensures your API endpoints are predictable and easy to use. In Rails, RESTful routing is a default, and it's important to follow this pattern for your resources. This structure promotes a clear separation between different types of requests (GET, POST, PUT, DELETE) and their corresponding actions (index, show, create, update, destroy), making the API easier to understand and maintain. For example, when designing API routes for a
Product
resource, you would use/products
for the index and/products/:id
for showing a single product.ActiveRecord: Rails’ ActiveRecord is a powerful ORM (Object-Relational Mapping) system that allows you to interact with your database using Ruby code rather than SQL queries. It abstracts away the complexity of SQL, provides a cleaner interface, and reduces the chance of SQL injection attacks. By relying on ActiveRecord for querying, you can write more maintainable code and use built-in methods like
.find
,.create
,.update
, and.destroy
to perform database operations safely and efficiently.
7.2 Securing APIs with JWT
JWT Authentication: JSON Web Tokens (JWT) are a compact, URL-safe method for representing claims to be transferred between two parties. In the context of a headless CMS, JWT is commonly used for securing API endpoints. After a user logs in with valid credentials, the server generates a JWT that contains the user’s identity and permissions. This token is sent to the client, which uses it to authenticate subsequent API requests. JWT is stateless, meaning that the server does not store any session data, improving scalability.
Token Expiry and Refresh: Always set an expiration time for your JWTs to limit the time window during which a token can be used. This helps mitigate the risks associated with token theft. You can implement short-lived access tokens and long-lived refresh tokens. The access token expires quickly (e.g., in 15 minutes), while the refresh token allows the user to obtain a new access token without requiring them to log in again. This provides a balance between user convenience and security.
Role-Based Access Control: Utilize the claims stored in the JWT to implement Role-Based Access Control (RBAC). RBAC allows you to assign different roles (e.g.,
admin
,user
,guest
) to your users and restrict access to resources based on their role. For example, an admin user might have the ability to create, update, and delete products, while a guest might only have access to read-only endpoints. Using the JWT claims to manage permissions helps maintain a secure and flexible access control model.
7.3 Validating Inputs and Preventing Invalid Data
Model Validations: In Rails, model validations ensure that only valid data is saved to the database. For example, you can use
validates_presence_of
to ensure required fields are not empty,validates_numericality_of
to ensure a field contains only numbers, andvalidates_uniqueness_of
to prevent duplicate entries. These validations are crucial for data integrity and help catch issues early. For a headless CMS, this might include validating fields like product names, prices, or categories before they’re persisted.Strong Parameters: Rails' strong parameters system is used to filter and permit only the attributes that should be allowed to be modified by the user. This prevents unwanted attributes from being accidentally or maliciously modified. For example, when creating or updating a
Product
, you might permit only certain fields likename
,description
, andprice
. By usingparams.require(:product).permit(:name, :description, :price)
, you ensure that only these attributes are used in the controller action, reducing the risk of mass-assignment vulnerabilities.Error Handling: Proper error handling is essential to providing a good API experience. When a user submits invalid data or an operation fails, returning a clear and helpful error message allows the consumer of your API to understand what went wrong. In Rails, this can be done by using the
errors
method on models and returning appropriate HTTP status codes (e.g., 400 for bad requests, 422 for unprocessable entity). For example, if a user tries to submit a product without a name, the response might include{"error": "Product name can't be blank"}
.
7.4 Optimizing Queries
Avoid N+1 Queries: N+1 queries occur when your application queries the database in a loop, resulting in a separate query being executed for each item in a collection. This can severely impact performance, especially for large datasets. To prevent N+1 queries, you should use
includes
orjoins
to eager load related models in a single query. For example, if you have aProduct
model that belongs to aCategory
, instead of loading each category separately withProduct.all.each { |product| product.category }
, you can useProduct.includes(:category).all
to fetch both products and categories in a single query.Indexes: Database indexes improve the speed of data retrieval operations by allowing the database to find rows more efficiently. Indexes are particularly important on columns that are frequently used in WHERE clauses, joins, or ordering. For example, if you often query products by
category_id
, you should add an index on thecategory_id
column in the products table. This will speed up queries and reduce the load on the database.Database Transactions: Use database transactions to ensure that your database operations are atomic. A transaction ensures that all changes are committed together or none at all. This is important when performing multiple related database operations, such as creating an order with multiple items. If an error occurs during any of the operations, the transaction is rolled back, and no data is written to the database. In Rails, you can use
ActiveRecord::Base.transaction
to wrap a block of code in a transaction.
Future Updates and Versions
8.1 Features to Expand Book Bazaar
As you continue to develop Book Bazaar, several key features can be added to enhance its functionality, scalability, and overall user experience:
Third-Party API Integration: To extend the capabilities of your CMS, integrating third-party APIs is essential. Payment gateways like Stripe or PayPal can facilitate secure online transactions, while shipping APIs (e.g., Shippo or UPS) can automatically calculate shipping rates and provide tracking information. This will streamline order processing and improve the customer experience.
GraphQL Support: Adding GraphQL support to your CMS can offer more flexibility compared to REST APIs. GraphQL allows clients to query exactly the data they need, improving performance by reducing unnecessary data transfers. This is especially useful in CMS applications where frontend clients often need to fetch a variety of data, such as product details, categories, and reviews.
Admin Dashboard: A feature-rich admin panel would greatly improve the ease of content management. Implementing an admin dashboard would allow non-technical users to manage products, view orders, track inventory, and handle user management without needing to interact directly with the database. Using gems like ActiveAdmin or RailsAdmin, you can quickly add a customizable, user-friendly interface.
User Authentication and Authorization: Adding user authentication (using gems like Devise) and role-based access control is crucial for any CMS where different users may have different levels of access. For instance, you could allow admins to manage all aspects of the CMS, while giving editors access to only content management, and customers access to their own orders. This will enhance security and improve the user experience.
Product Variants: Many e-commerce platforms require support for product variants (e.g., different sizes, colors, and materials). By implementing this feature, you can allow customers to choose from various options of a product, each with its own unique pricing and inventory status. This could be achieved by extending the Product model to include associations for variants and attributes.
Real-Time Features: With the addition of WebSockets or ActionCable, you can introduce real-time features into Book Bazaar. For example, you could notify users of live updates, such as stock availability or the status of their orders. Real-time capabilities would create a more dynamic and interactive experience for users, which is especially useful in fast-paced environments like e-commerce.
8.2 New Features in Upcoming Rails Versions
Ruby on Rails is actively evolving, and keeping up with its updates can help you improve Book Bazaar's performance, security, and maintainability. Here are some anticipated changes that could benefit the platform:
Rails 8 (or Later Versions): With the release of Rails 8 (or later versions), there may be improvements in API mode and updates that streamline the process of building API-only applications. Additionally, updates to default configurations and optimizations for database connections will likely enhance performance, making it easier to scale Book Bazaar as the user base grows. Keeping up with these updates will ensure that the application remains modern and efficient.
Active Record Enhancements: In future versions of Rails, expect continued enhancements to Active Record, particularly around handling complex queries, database joins, and large datasets. With features like materialized views and window functions, handling high-traffic databases and large catalogs of products will become much more efficient, leading to improved performance in querying product data, customer records, and transactions.
Improved Background Jobs: Background jobs are essential for tasks that are time-consuming or resource-intensive, such as sending order confirmation emails or processing payments. Future versions of Rails may provide more advanced features for background job processing, such as better integration with Sidekiq or improvements to ActiveJob that can help process jobs more efficiently. This would result in faster processing times and less strain on the main application server.
Better Webpacker/Importmap Integration: The management of front-end assets is an ever-evolving space in Rails. With updates to Webpacker and Importmap, developers will likely experience better handling of JavaScript and CSS. For instance, Webpacker may become even more intuitive with bundling assets and integrating frameworks like React or Vue.js. For simpler projects, Importmap may make asset management even more lightweight, enabling seamless use of JavaScript without the need for a separate build step.
Security Enhancements: Rails is known for its strong security features, and future versions will likely continue to focus on improving security practices. This could include updates to cross-site scripting (XSS) protection, content security policies, and encrypted cookies. Staying up to date with these security enhancements will help protect Book Bazaar from potential vulnerabilities and ensure the safety of user data.
Compatibility with Ruby Versions: As Ruby continues to evolve, Rails will need to maintain compatibility with the latest Ruby versions. Future updates may include optimizations for Ruby 3.1 or Ruby 4, taking advantage of new language features and performance improvements.
Conclusion
By keeping track of upcoming Rails versions, you can ensure that Book Bazaar stays up to date with the latest features, improving both its functionality and its long-term viability.
In this tutorial, we have successfully walked through the process of building a headless CMS using Ruby on Rails. From setting up the development environment and configuring the database to implementing advanced features like pagination, SEO optimization, and API management, you’ve gained the essential skills required to build a scalable and maintainable CMS.
The flexibility of a headless CMS, coupled with the powerful tools provided by Rails, enables developers to create modern, API-first applications that can integrate seamlessly with a wide range of front-end frameworks. As you continue to expand your knowledge of Rails and explore further advancements in web development, the concepts covered in this tutorial will serve as a solid foundation for building dynamic, content-driven websites and applications.
The field of headless CMS is growing rapidly, and mastering it opens opportunities to work with cutting-edge technologies and design solutions that meet the demands of modern digital platforms. We encourage you to continue experimenting with these concepts and explore new ways to enhance and optimize your CMS projects.
References
Ruby on Rails Official Documentation
https://guides.rubyonrails.orgKaminari Pagination Gem
https://github.com/kaminari/kaminariFriendlyId SEO Slug Gem
https://github.com/norman/friendly_idDocker Documentation
https://docs.docker.com/MySQL Documentation
https://dev.mysql.com/doc/Postman API Testing Tool
https://www.postman.com/Headless CMS Trends
"The Rise of Headless CMS and What it Means for Developers"Rails API Best Practices
https://medium.com/rubycademy/rails-api-best-practicesShopify Headless CMS
https://www.shopify.com/enterprise/headless-commerce