DEV Community

Aria Diniz
Aria Diniz

Posted on

Simplifying Backend APIs with Macaw Framework: A CRUD Tutorial

If you're tired of configuring a lot of parameters and things you wouldn't even use when creating simple backend APIs, you might want to check out Macaw Framework. In my previous post, I introduced Macaw Framework, a lightweight and flexible web framework. Now, I'd like to show you how to use Macaw Framework to build a simple CRUD application.

It's worth noting that, despite being production ready, Macaw Framework is still in its first beta version. I encourage you to try it out, give feedback, and report any issues you encounter. Your feedback will be invaluable in helping me to improve the framework and make it even better for the community.

In this article, I'll guide you through the steps to build a simple CRUD application using Macaw Framework. You can find the code for this application on GitHub here. If you're not familiar with Macaw Framework, I encourage you to read my previous post on the topic to get a better understanding of how the framework works.

Macaw Framework is designed to be simple and easy to use, with sensible defaults that make it easy to get started. It's also highly customizable, with a wide range of options for configuring your application to meet your needs. Whether you're building a simple API or a complex web application, Macaw Framework can help you get the job done quickly and efficiently.

Step 0: Create and Configure Your application.json File

Before we dive into building our CRUD application, we need to create and configure our application.json file. This file will contain configuration options for our Macaw Framework application.

Create a new file called application.json in the root directory of your project and add the following contents:

{
  "macaw": {
    "threads": 10,
    "bind": "0.0.0.0",
    "port": 8080,
    "cache": {
      "cache_invalidation": 10,
      "ignore_headers": [
        "Postman-Token"
      ]
    },
    "prometheus": {
      "endpoint": "/metrics"
    },
    "rate_limiting": {
      "window": 10,
      "max_requests": 10
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This file contains various options that will be used by Macaw Framework to configure your application. For example, the threads option specifies the number of threads that should be used by the application, while the cache option configures caching behavior. You can adjust these options to suit the needs of your application.

Once you've created your application.json file, you're ready to start building your CRUD application using Macaw Framework!

Step 1: Set Up Your Environment

To get started, create a new directory for your project and navigate into it. Then, add a Gemfile with the following content:

# frozen_string_literal: true

source "https://rubygems.org"

gem "sqlite3"

gem "yaml"

gem "json"

gem "macaw_framework"

gem "activerecord"

Enter fullscreen mode Exit fullscreen mode

For this demo project I'm using a SQLite3 database. You can find the database and everything else in the project's repository.

Next, run bundler in order to install the dependencies:

bundle install
Enter fullscreen mode Exit fullscreen mode

Using Bundler makes it easier to manage dependencies for your project, and ensures that all team members are using the same versions of the required gems. Once you have your dependencies installed, you're ready to start building your application with Macaw Framework!

Step 2: Define Your Person Model

Create a new file called person.rb in a subdirectory called lib/entity. This file will define your Person model, which represents a person in your database.

class Person < ActiveRecord::Base
  table_name = 'people'
end
Enter fullscreen mode Exit fullscreen mode

Step 3: Create Your App File

Create a new file called app.rb in the root directory of your project. This file will be the entry point for your application, and it will define your Macaw Framework endpoints.

# frozen_string_literal: true

require 'macaw_framework'
require 'active_record'
require 'yaml'
require_relative './lib/entity/person'
require_relative './lib/errors/unprocessable_body_error'

# Configuring ActiveRecord
db_config = File.open('./db/database.yaml')
ActiveRecord::Base.establish_connection(YAML.safe_load(db_config, aliases: true))

# Instantiating MacawFramework Class
server = MacawFramework::Macaw.new
Enter fullscreen mode Exit fullscreen mode

Step 4: Define Your GET Endpoint

The first endpoint we'll define will allow us to retrieve a list of all people in the database. Add the following code to your app.rb file:

# Defining a GET endpoint to list all persons in the database
server.get('/people', cache: true) do |_context|
  return JSON.pretty_generate(Person.all.as_json), 200, {"Content-Type" => "application/json", "random-header" => "random value"}
end
Enter fullscreen mode Exit fullscreen mode

Step 5: Define Your GET by Id Endpoint

The second endpoint we'll define will allow us to retrieve a specific person from the database by their ID. Add the following code to your app.rb file:

# Defining a GET endpoint to recover person with provided id
server.get('/people/:person_id') do |context|
  return JSON.pretty_generate(Person.where(id: context[:params][:person_id]).first.as_json), 200
end
Enter fullscreen mode Exit fullscreen mode

Step 6: Define Your POST Endpoint

The third endpoint we'll define will allow us to create a new person in the database. Add the following code to your app.rb file:

server.post('/add_new_person') do |context|
  begin
    parsed_body = JSON.parse(context[:body])
    name = parsed_body['name']
    age = parsed_body['age']
    raise UnprocessableBodyError if name.nil? || age.nil?

    Person.create!(name: name, age: age)
    return JSON.pretty_generate({ message: "Person created with success!!!" }), 201
  rescue UnprocessableBodyError => e
    return JSON.pretty_generate({ error: e }), 422
  rescue StandardError => e
    return JSON.pretty_generate({ error: e }), 500
  end
end
Enter fullscreen mode Exit fullscreen mode

This endpoint will create a new Person record in the database based on the provided request body, which should be a JSON object with name and age properties. If the request body is missing either property, we'll raise our custom UnprocessableBodyError and return a 422 Unprocessable Entity status code with an error message.

Note that we're also raising a new custom error class, UnprocessableBodyError, if the request body is missing the required properties. To define this error class, create a new file called unprocessable_body_error.rb in your lib/errors directory with the following contents:

class UnprocessableBodyError < StandardError
  def initialize(
    msg = 'Please inform a body on your request in JSON format with a "name" and "age" properties'
  )
    super
  end
end
Enter fullscreen mode Exit fullscreen mode

This error will be rescued in our PATCH and POST endpoint and return a 400 Bad Request status code with an error message if the request body is missing the required properties.

Step 7: Define Your PATCH Endpoint

The fourth endpoint we'll define will allow us to update an existing person in the database. Add the following code to your app.rb file:

server.patch('/people/:person_id') do |context|
  raise UnprocessableBodyError.new unless context[:params][:person_id]

  body = JSON.parse(context[:body])
  name = ActiveRecord::Base.connection.quote(body["name"])
  age = ActiveRecord::Base.connection.quote(body["age"])
  Person.update(
    context[:params][:person_id].to_i,
    name: name,
    age: age
  )
  [JSON.pretty_generate({ message: "Updated person #{context[:params][:person_id]}" }), 200]
rescue UnprocessableBodyError
  ["Person with id #{context[:params][:person_id]} does not exist", 400]
rescue StandardError => e
  [JSON.pretty_generate(e.message), 500]
end
Enter fullscreen mode Exit fullscreen mode

Step 8: Define Your DELETE Endpoint

The fifth and final endpoint we'll define will allow us to delete an existing person from the database. Add the following code to your app.rb file:

# Defining a DELETE endpoint to delete an existing person in the database
server.delete('/delete_person') do |context|
  begin
    parsed_body = JSON.parse(context[:body])
    id = parsed_body['id']
    raise UnprocessableBodyError.new('Please inform a JSON body with an "id" parameter') if id.nil?

    person = Person.find_by(id: id.to_i)
    if person.nil?
      return JSON.pretty_generate({ error: "Person with ID #{id} not found." }), 404
    else
      person.destroy
      return JSON.pretty_generate("Person with ID #{id} deleted."), 200
    end
  rescue UnprocessableBodyError => e
    return JSON.pretty_generate({ error: e.message }), 422
  rescue StandardError => e
    return JSON.pretty_generate({ error: e }), 500
  end
end
Enter fullscreen mode Exit fullscreen mode

Step 9: Start Your Server

Finally, add the following code to your app.rb file to start the server:

# Start Server
server.start!
Enter fullscreen mode Exit fullscreen mode

Now that you've defined all of your endpoints, you're ready to start your server and test your application! You can do this by running the following command:

ruby app.rb
Enter fullscreen mode Exit fullscreen mode

Your server will start running on http://localhost:8080/. You can use tools like Postman or cURL to send requests to your endpoints and test your application.

Now that we've defined all of our endpoints, we're ready to start our server and test our application!

Top comments (0)