Part 1 - Setting up Monorepo, APIs and frontend components.

koushikmohan1996

Koushik KM

Posted on March 7, 2020

Part 1 - Setting up Monorepo, APIs and frontend components.

In this series, I will be covering on how to get started with Ruby on Rails, Mongo DB and React stack or I call them R3M. I will not bore you with a lot of explanation, So if you stuck at any point feel free to ask in comment. Being said that I assume you have a basic understanding of these languages and installed all the required software and tools. Okay, let's hop in. :)

TLDR: If you are bored to read step by step process and want to figure things by yourself kindly check my example repository.

Github repository link: https://github.com/koushikmohan1996/ror-react-mongo

Monorepo

In this example, I will be using monorepo architecture. Monorepo will be very efficient for small apps and it is very easy to maintain. CI and CD will be very easy and we can make atomic commits in monorepo structure. You can read about Monorepo and other architecture online.

Setup

Create 2 folders server and client before proceeding to next steps

1. RoR

Setting up and running an RoR app is very simple. They have an excellent document on how to do that. Since we are using react for our frontend we don't need View support from rails. Also, we will be using mongoid as ORM so we can avoid default ORM (active record) support from rails. Run the following command to create a Rails app.

rails new server --api --skip-active-record
Enter fullscreen mode Exit fullscreen mode

Add the below line in Gemlock file to add ORM support provided by mongo

gem 'mongoid', '~> 7.0.5'
Enter fullscreen mode Exit fullscreen mode

Generate Mongo configuration file using the following command

rails g mongoid:config
Enter fullscreen mode Exit fullscreen mode

Start rails server

rails s
Enter fullscreen mode Exit fullscreen mode

2. React

Create a react app with the following command

npx create-react-app client
Enter fullscreen mode Exit fullscreen mode

Now start the react app with npm/yarn command (according to your package manager)

yarn start (or) npm start
Enter fullscreen mode Exit fullscreen mode

Note: Rails app may run in the same port of React. I this case React will automatically change its port.

Backend API

To make everything simple, I will be building a simple notes app that can store title and content as a note.
No authentication! No complex DB structure!. If you need an example of authentication in Rails, I will cover it in a separate article.

As a first step, we should create a model to store notes details. Rails provide an easy way to generate these models using rails generator commands. You can learn them in detail from their official document.

rails g model Note title:String content:String
Enter fullscreen mode Exit fullscreen mode

This command will create a model notes.rb in the models folder. You can check if the fields are correctly added to it.

Resources can be used for creating routes. It will support API architecture (get, post, put delete) by default. Add resources :notes to routes.rb file. You can now check the list of supported routes using command rails routes

Add a controller using the following generator command.

rails g controller notes
Enter fullscreen mode Exit fullscreen mode

If you access http://localhost:3000/notes in browser or postman, it will throw an error saying that the action show is not defined in the NotesController. Let's go ahead and define it. Add the following line to notes_controller.rb

# GET /notes
  def index
    @notes = Note.all

    render json: @notes
  end
Enter fullscreen mode Exit fullscreen mode

Similarly, you can add other methods for CRUD operation. You can also skip everything and use rails generate scaffold Note title:string content:string to generate Models, Controller, and routes. Refer notes_controller.rb in the example repository for all CRUD operations.

Since we are using React server and may host frontend in as separate service, we should add cors support. To do that add gem 'rack-cors' to Gemlock file and the below code to application.rb. You don't have to use a GEM for this but it provides a lot of customization which can be used later.

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
      resource '*', headers: :any, methods: [:get, :post, :options, :delete, :put]
  end
end
Enter fullscreen mode Exit fullscreen mode

API server is almost ready now. You can check it by adding notes using any API tool such as Postman. If you check the API response you will see something like this:

[
  {
    "_id":{
       "$oid":"5e622d49a394011924987ee3"
     },
    "content":"Sample notes",
    "title":"Test"
  }
]

Enter fullscreen mode Exit fullscreen mode

Even though it is good enough, getting the id of the notes is tedious in frontend with this response. It will be much easier if we get the id directly as a string rather than an object with an additional layer. Add an as_json method in models/note.rb to override its default behavior. Now check the response again.

def as_json(*args)
  res = super
  res['id'] = self.id.to_s
  res
end
Enter fullscreen mode Exit fullscreen mode

With this API server is ready and we can move forward to frontend. Yay!

Frontend

I prefer React over other frontend frameworks. React highly flexible and small in size. But you may need additional packages like Router, Redux to build big applications. But I am not going to use these in this tutorial. Also, I will be using hooks based on components rather than class-based components. If you never worked on hooks you can check React's official document.

There are many ways to organize files in your React app and this can change based on the size of your app. Create two folders screens and service in client and create a file APIservice.js under service folder.

const API_URL = 'http://localhost:3000'

export const addNote = (title, content) => {
  return fetch(`${API_URL}/notes`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ title, content }),
  }).then(res => res.json());
};
Enter fullscreen mode Exit fullscreen mode

You can add other methods similarly or you can copy it from the repo.

Instead of writing a single big component break your components into small pieces. It will be very easy to write and maintain many small components rather than a single big component. In our case, we will be splitting the MainPage.js into 3 components.

  • A form to get inputs and submit it.
  • A card component to display notes
  • A container component to hold these components.

To make it simple am adding all the components in single file. But you can opt to create a components folder and maintain each component separately.

Getting inputs from a form

const NotesForm = (props) => {

  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = (evt) => {
    evt.preventDefault();
    addNote(title, content)
      .then(props.handleNoteAdded)
      .catch(e => setError('Error occurred while adding note'));
  };

  return (
    <div>
      <form style={{ display: 'inline-grid' }} onSubmit={handleSubmit}>
        <input type="text" placeholder="Title" className="input" onChange={e => setTitle(e.target.value)} value={title} />
        <textarea type="text" placeholder="Content" className="input" onChange={e => setContent(e.target.value)} value={content} />
        <input type="Submit" value="Add Notes" className="input" />
        <p className="error">
          {error}
        </p>
      </form>
    </div>
  )
};
Enter fullscreen mode Exit fullscreen mode

Card to display notes

const NotesCard = (props) => {
  const { title, content, id } = props;

  const handleDelete = () => {
    deleteNote(id).then(props.onDelete);
  };

  return (
    <div className="card">
      <span><b>{title}</b></span>
      <p>{content}</p>

      <button onClick={handleDelete} className="card-button">Delete</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Now you can use these 2 components to compose a new component which will be our main page. Check out MainPage.js for the entire code.

<div className="main-page-body">
  <NotesForm handleNoteAdded={getNotes}/>
  <div className="card-container">
    {
      notes.map(notesObj => <NotesCard
        content={notesObj.content}
        title={notesObj.title}
        id={notesObj.id}
        onDelete={getNotes}
      />)
    }
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Motivation

With this a basic version of Notes app ready. I am new to Ruby on Rails and am not an expert in React too and that is the motivation to start this series. This series will have tutorials, information which I wish I would see in a single place to kick start my project. In the next article, I will cover about writing tests for React and Rails app. If you want anything, in particular, feel free to drop a comment.

Happy to help :)

💖 💪 🙅 🚩
koushikmohan1996
Koushik KM

Posted on March 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related