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
Add the below line in Gemlock file to add ORM support provided by mongo
gem 'mongoid', '~> 7.0.5'
Generate Mongo configuration file using the following command
rails g mongoid:config
Start rails server
rails s
2. React
Create a react app with the following command
npx create-react-app client
Now start the react app with npm/yarn command (according to your package manager)
yarn start (or) npm start
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
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
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
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
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"
}
]
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
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());
};
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>
)
};
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>
)
}
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>
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 :)
Top comments (0)