Mern Stack Crud App Using create-react-app & React Redux Part 1.

Bipin Swarnkar
7 min readAug 26, 2017

Read Part 2.

The MERN (MongoDB, Express, ReactJs, Node.js) stack is arguably the most popular stack for any web application these days. Our application is a basic crud application tutorial that will help you get up to speed with React quickly, and also build a complete app with the MERN stack.

To explain the different parts:

  • MongoDB is a document database with the scalability and flexibility that you want with the querying and indexing that you need.
  • Express, a web server built on Node.js, formed the middle tier, or the web server.
  • ReactJs, a JavaScript library for building user interfaces,
  • NodeJs, a server-side JavaScript runtime environment.
  • Create React App is the best way to start building a new React single page application. It sets up your development environment so that you can use the latest JavaScript features, provides a nice developer experience, and optimizes your app for production.
  • Redux is a predictable state container for JavaScript apps.

The GitHub Repo for this tutorial is available here. I recommend checking it out, because its got this whole tutorial.

Prerequisites

  • Basic understanding of ReactJs, NodeJs and MongoDB.
  • ES6 Javascipt — I will try to make note when using something from it.
  • Node/NPM installed.
  • MongoDB installed.

Okay, so lets get started!

Step 1 . Create express app

The first thing we will do is to create a express Rest Api . Create a directory to hold your application, and make that your working directory:

mkdir express-server && cd express-server

Use the npm init command to create a package.json file for your application.

npm init

This command prompts you for a number of things, such as the name and version of your application. For now, you can simply hit RETURN to accept the defaults for most of them, with the following exception:

entry point: (index.js)

Enter app.js, or whatever you want the name of the main file to be. If you want it to be index.js, hit RETURN to accept the suggested default file name.

Now install the Express library in the express-server directory and save it in the dependencies list (you can omit the --save option if for some reason you don't want your package.json to include Express)

npm install express --save

Installing our node packages

npm install body-parser mongoose morgan babel-preset-es2015-node6 source-map-support --save

body-parser will let us pull POST content from our HTTP request. mongoose is the ORM we will use to communicate with our MongoDB database. morgan is used for logging request details. babel-preset-es2015-node6 is Babel preset to make node@6 fully ES2015 compatible. source-map-support, Because of the compilation, all errors will now be reported as if they occurred at the line number in the compiled file, not the source file. For a better reporting, we need to generate source maps and tell Node.js to use the source maps.

Setting Server file. Create a file in root called “app.js.” This file is our main server file in which it bootstraps the node server, routes, models and also it serves some static files. Put the following code in it

// ./express-server/app.js
import express from 'express';
import path from 'path';
import bodyParser from 'body-parser';
import logger from 'morgan';
import mongoose from 'mongoose';
import bb from 'express-busboy';
// import routes
import todoRoutes from './routes/todo.server.route';
// define our app using express
const app = express();
// express-busboy to parse multipart/form-data
bb.extend(app);
// allow-cors
app.use(function(req,res,next){
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
})
// configure app
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended:true }));
app.use(express.static(path.join(__dirname, 'public')));
// set the port
const port = process.env.PORT || 3001;
// connect to database
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/mern-todo-app', {
useMongoClient: true,
});
app.use('/api', todoRoutes);app.get('/', (req,res) => {
return res.end('Api working');
})
// catch 404
app.use((req, res, next) => {
res.status(404).send('<h2 align=center>Page Not Found!</h2>');
});
// start the server
app.listen(port,() => {
console.log(`App Server Listening at ${port}`);
});

The above code sets up a express app, with an /api route points to a file ./routes/todo.server.route . Let’s create this file.

./express-server/routes/todo.server.route.js

// ./express-server/routes/todo.server.route.js
import express from 'express';
//import controller file
import * as todoController from '../controllers/todo.server.controller';
// get an instance of express router
const router = express.Router();
router.route('/')
.get(todoController.getTodos)
.post(todoController.addTodo)
.put(todoController.updateTodo);
router.route('/:id')
.get(todoController.getTodo)
.delete(todoController.deleteTodo);
export default router;

Create a controller file todo.server.controller.js in a new folder called controllers to handle the CRUD functionality at the backend.

./express-server/controllers/todo.server.controller.js

// ./express-server/controllers/todo.server.controller.js
import mongoose from 'mongoose';
//import models
import Todo from '../models/todo.server.model';
export const getTodos = (req,res) => {
Todo.find().exec((err,todos) => {
if(err){
return res.json({'success':false,'message':'Some Error'});
}
return res.json({'success':true,'message':'Todos fetched successfully',todos});
});
}
export const addTodo = (req,res) => {
const newTodo = new Todo(req.body);
newTodo.save((err,todo) => {
if(err){
return res.json({'success':false,'message':'Some Error'});
}
return res.json({'success':true,'message':'Todo added successfully',todo});
})
}
export const updateTodo = (req,res) => {
Todo.findOneAndUpdate({ _id:req.body.id }, req.body, { new:true }, (err,todo) => {
if(err){
return res.json({'success':false,'message':'Some Error','error':err});
}
console.log(todo);
return res.json({'success':true,'message':'Updated successfully',todo});
})
}
export const getTodo = (req,res) => {
Todo.find({_id:req.params.id}).exec((err,todo) => {
if(err){
return res.json({'success':false,'message':'Some Error'});
}
if(todo.length){
return res.json({'success':true,'message':'Todo fetched by id successfully',todo});
}
else{
return res.json({'success':false,'message':'Todo with the given id not found'});
}
})
}
export const deleteTodo = (req,res) => {
Todo.findByIdAndRemove(req.params.id, (err,todo) => {
if(err){
return res.json({'success':false,'message':'Some Error'});
}
return res.json({'success':true,'message':todo.todoText+' deleted successfully'});
})
}

Create a model for Mongoose Schema. So create a new directory in the express-server folder called models and then create another file in that called todo.server.model.js .

./express-server/models/todo.server.model.js

import mongoose from 'mongoose';var Schema = mongoose.Schema({
createdAt:{
type: Date,
default: Date.now
},
fullName: String,
todoText: String
});
export default mongoose.model('Todo', Schema);

Even though Node.js supports many ES2015 features, it is not complete. For example,you still need to use require instead of import to include other modules.If you run node app.js , it wil give the error given below.

import express from 'express';^^^^^^SyntaxError: Unexpected token importat createScript (vm.js:56:10)at Object.runInThisContext (vm.js:97:10)at Module._compile (module.js:542:28)at Object.Module._extensions..js (module.js:579:10)at Module.load (module.js:487:32)at tryModuleLoad (module.js:446:12)at Function.Module._load (module.js:438:3)at Module.runMain (module.js:604:10)at run (bootstrap_node.js:390:7)at startup (bootstrap_node.js:150:9)

Fortunately, there are other babel presets available that let you use what is available in Node.js and change only unsupported ES2015 features. Depending on the version of Node.js, the presets are named differently, corresponding to the version. For Node.js 6, it’s called babel-preset-es2015-node6. Let’s now install this and use the preset instead of the default es2015 preset:

npm install --save babel-preset-es2015-node6

For Node.js version 6, it is babel-preset-es2015-node6. Further, the compilation only takes care of the new syntax of ES2015, but does not provide new objects and methods that are part of the ES2015 standard library.

Because of the compilation, all errors will now be reported as if they occurred at the line number in the compiled file, not the source file. For a better reporting, we need to generate source maps and tell Node.js to use the source maps. To let Node.js report line numbers by using source maps, we need to install the source-map-support module, and also call the library in the application once. Let’s first install the package.

npm install --save source-map-support

To use this , we need to change app.js as shown below.

app.js: Complete new app.js file with added Source Map Support

// ./express-server/app.js
import express from 'express';
import path from 'path';
import bodyParser from 'body-parser';
import logger from 'morgan';
import mongoose from 'mongoose';
import SourceMapSupport from 'source-map-support';
// import routes
import todoRoutes from './routes/todo.server.route';
// define our app using express
const app = express();
// allow-cors
app.use(function(req,res,next){
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
})
// configure app
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended:true }));
app.use(express.static(path.join(__dirname, 'public')));
// set the port
const port = process.env.PORT || 3001;
// connect to database
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/mern-todo-app', {
useMongoClient: true,
});
// add Source Map Support
SourceMapSupport.install();
app.use('/api', todoRoutes);app.get('/', (req,res) => {
return res.end('Api working');
})
// catch 404
app.use((req, res, next) => {
res.status(404).send('<h2 align=center>Page Not Found!</h2>');
});
// start the server
app.listen(port,() => {
console.log(`App Server Listening at ${port}`);
});

As we know Node.js does not full support es6. We have to do static compilation that can do the transformation of es6 code to es6 on the fly. This method is called the require hook. That’s because it binds itself to Node.js’ require and dynamically compiles any new module that is loaded. For this we have to install another dependency babel-register

npm install --save babel-register

To use it, let’s create a separate entry point, load the babel-register module, and then load your server.js to start the server. Let’s call this new file app_hooked.js; the contents of this file are shown below.

./express-server/app_hooked.js

// ./express-server/app_hooked.js
require('babel-register')({
presets: ['es2015-node6']
});
require('./app.js');

To automatically restart the server on changes, let’s install and use the package nodemon.

npm install -g nodemon

Here are the changes in package.json:

...
"scripts": {
"start": "nodemon app_hooked.js"
},
...

Now Start the Express Server

npm start

To test it, you can just type http://localhost:3001/ in the browser’s URL.

Express Server Screenshot

Also test the Api in POSTMAN (Postman is the most complete API Development Environment).

http://localhost:3001/api

Finally the Server part is completed. Lets dive into the React.js part.

Up Next…

We just had finished the the Server part. In the next part of this tutorial, we will create our React frontend app with Redux.

--

--