An Easy Way to Get Started with the MERN Stack

Dustin Byers

The MERN stack consists of MongoDB, Express, React / Redux, and Node.js. Given the popularity of React on the frontend and of Node.js on the backend, the MERN stack is one of the most popular stack of technologies for building a modern single-page app.

Here’s a breakdown of the typical stack setup:

MongoDB as a NoSQL database

  • An open source document database that provides persistence for application data.
  • Bridges the gap between key-value stores (fast and scalable) and relational databases (rich functionality).
  • Stores data as JSON documents in collections with dynamic schemas.
  • Designed with scalability and developer agility in mind.
  • Designed to be used asynchronously thus it pairs well with Node.js applications.

Express web framework of Node.js provides routing and middleware

  • Basically runs the backend code as a module within the Node.js environment.
  • Handles the routing of requests to the right parts of your app.

React.js provides a dynamic frontend

  • A JavaScript library developed by Facebook to build interactive / reactive interfaces.

Node.js on the server

  • A javascript runtime environment.
  • An asynchronous event-driven engine - which means the app makes a request for some data and then performs other tasks while waiting for a response.

The MERN stack wasn’t always the frontrunner in the JavaScript web app game. Many companies and developers still use the MEAN stack, which is identical to the MERN stack except it uses AngularJS or Angular instead of React for the frontend. One of the distinguishing features from React is the virtual DOM manipulation, which helps keep apps dynamic and fast.

Before Getting Started

There are plenty of starter-kits out there for people who just want to get a CRUD full-stack app up and running. These tools won’t give you everything you need for a MERN app but they will give you a head start.

  • Create React App provides a simple npm package to create a React App with no build configuration.
  • mern.io provides a boilerplate project and a command line interface utility.

In this post we’ll setup everything from scratch however, to give you a better idea of how the pieces fit together.

Getting Started

  • Create a directory on your computer called mern-starter (or whatever you like).
  • cd into that directory.
  • Create a backend and a frontend repo on github or using the command line.
  • Clone both repos into the mern-stack directory.
  • cd into the backend repo and run npm init -y.
  • Next, run touch index.js README.md .eslintrc.json .eslintignore .gitignore .env.
  • Then, run npm i -S express http-errors mongoose superagent body-parser cors dotenv.
  • Finally, run npm i -D jest eslint.

With that, we’ve got our basic scaffolding setup for the backend. Go ahead and configure your .eslintrc.json, .eslintignore, .gitignore and .env.

Your .env file should contain your environment variables:

.env

PORT=3000
DEBUG=true
API_URL=http://localhost:3000
CORS_ORIGINS=http://localhost:8080
APP_SECRET='something secret'
MONGODB_URI=mongodb://localhost/mern-starter

Your package.json should have all of the installed dependencies as well as these scripts:

package.json

"scripts": {
  "lint": "eslint ./",
  "test": "jest -i --verbose --coverage --runInBand",
  "start": "node index.js",
  "dboff": "killall mongod",
  "watch": "nodemon index.js",
  "dbon": "mkdir -p ./db && mongod --dbpath ./db"
},

Next, we’re going to set up our server:

  • Navigate into your editor and open your project.
  • From the root of your backend repo create a src folder.
  • Inside of the src folder create a main.js file, a lib folder, a middleware folder, a model folder, and a route folder.

Navigate into the lib folder and create a server.js file - this is where we are going to set up our server and connect it to MongoDB:

server.js

'use strict'

import cors from 'cors';
import express from 'express';
import mongoose from 'mongoose';
import bodyParser from 'body-parser';

const app = express();
const router = express.Router();

// env variables
const PORT = process.env.PORT || 3000;
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost/mern-starter';

mongoose.Promise = Promise;
mongoose.connect(MONGODB_URI);

app.use(bodyParser.json(),cors())

app.use(require('../route/auth-router'));

app.all('*', (request, response) => {
  console.log('Returning a 404 from the catch-all route');
  return response.sendStatus(404);
});

// error middleware
app.use(require('./error-middleware'));


export const start = () => {
  app.listen(PORT, () =>{
    console.log(`Listening on port: ${PORT}`)
  })
}

export const stop = () => {
  app.close(PORT, () => {
    console.log(`Shut down on port: ${PORT}`)
  })
}

This is a very basic server setup. At the top of our file we are importing our npm packages. We are setting up our express router and connecting to mongoose, then requiring-in our routes and middleware. We then export the start and stop variables that turn our server off and on and log what PORT we are on.

The next thing we need is to go into our index.js file at the root of our backend repo and require('./src/lib/server').start(). This is requiring-in our server file and starting the server.

Schema and Models

Keep in mind you should create at least one model and one route before trying to start the server. Below you’ll find a sample mongoose Schema (mongoose is mongoDB object modeling for node) and a sample route.

We’ll create a user Schema, aka a user model. Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection. Here we are declaring all of the properties we can expect users to have attached to their model, what data type they are, if they are unique or not, and if we want them to be required or not.

In this same file we can create methods like User.create() which is how we create new users.

models/user.js

const userSchema = mongoose.Schema({
  passwordHash: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  username: { type: String, required: true, unique: true },
  tokenSeed: { type: String, required: true, unique: true },
  created: { type: Date, default : () => new Date()},
});

const User = module.exports = mongoose.model('user', userSchema)

And then in the following snippet are auth routes - we’re using express’s Router to create routes and endpoints allowing a user to signup or login to our app:

routes/user.routes.js

'use strict'

import { Router } from 'express';
import bodyParser from 'body-parser';

import basicAuth from '../lib/basic-auth-middleware.js'
import User from '../model/user.js';

const authRouter = module.exports = new Router();

authRouter.post('/api/signup', jsonParser, (req, res, next) => {
  console.log('hit /api/signup')

  User.create(req.body)
  .then(token => res.send(token))
  .catch(next)
})

authRouter.get('/api/login', basicAuth, (req, res, next) => {
  console.log('hit /api/login')

  req.user.tokenCreate()
  .then(token => res.send(token))
  .catch(next)
})

Now we should be able to go to our terminal, make sure we cd into the backend repo, and run the command npm run dbon, this should start mongodb. Then open a new backend terminal tab and run the command npm run start.

And that’s it for a bare minimum backend setup! Let’s now set up a very basic frontend with React.

Frontend Setup

  • cd into the frontend directory and run npm init -y to create a package.json file.
  • Next, run touch README.md .eslintrc.json .eslintignore .gitignore .env .babelrc webpack.config.js.
  • Then, run npm i -S babel-core babel-loader babel-plugin-transform-object-rest-spread babel-preset-env babel-preset-react css-loader extract-text-webpack-plugin html-webpack-plugin node-sass react react-dom resolve-url-loader sass-loader webpack webpack-dev-server babel-cli.
  • Be aware that extract-text-webpack-plugin is now deprecated in webpack 4.0.0.
  • Then run npm i -D jest eslint.

Open your code editor and add these scripts to your package.json:

package.json

"scripts": {
  "lint": "eslint . ",
  "build": "webpack",
  "watch": "webpack-dev-server --inline --hot"
},

We're using the --hot flag to enable webpack's Hot Module Replacement.

Create a src folder at the root of. Inside of src create a main.js file that has:

main.js

import React from 'react';
import ReactDom from 'react-dom';
import App from './components/app';

const container = document.createElement('div');
document.body.appendChild(container);

ReactDom.render(<App />, container);

Inside of src create a components folder and inside of the component folder create a folder named app. Inside of the app folder create an index.js that has the following code:

components/app/index.js

import React from 'react';

class App extends React.Component {
  constructor(props){
    super(props);
  }

 render() {
   return (
     <div>
        <h1>MERRRRRN</h1>
     </div>
   );
 }
}

export default App;

Plus, let’s add a webpack confiration file:

webpack.config.js

'use strict'

const ExtractPlugin = require('extract-text-webpack-plugin')
const HTMLPlugin = require('html-webpack-plugin')
module.exports = {
  devtool: 'eval',
  entry: `${__dirname}/src/main.js`,
  output: {
    filename: 'bundle-[hash].js',
    path: `${__dirname}/build`,
    publicPath: '/',
  },
  plugins: [
    new HTMLPlugin(),
    new ExtractPlugin('bundle-[hash].css'),
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_module/,
        loader: 'babel-loader',
      },
      {
        test: /\.scss$/,
        loader: ExtractPlugin.extract(['css-loader', 'sass-loader']),
      },
    ],
  },
}

We're using the Sass loader, so your project can make use of Sass files for styling.


Here’s also our Babel configuration file:

.babelrc

{
  "presets": ["env", "react"],
  "plugins": ["transform-object-rest-spread"]
}
  • The Babel env preset automatically determines the Babel plugins you need based on your supported environments.
  • The Babel react preset converts JSX syntax and strips out type annotations.
  • The Babel plugin transform-object-rest-spread transforms rest properties for object destructuring assignment and spread properties for object literals.

Now - in the frontend repo terminal window run the command npm run watch and navigate to the localhost url in the browser. Everything should now be working! 😅

Connecting the Frontend to the Backend

Here’s an example of a request made inside of a handleSubmit(e) method, inside of a SearchForm component.

SearchForm.js

handleSubmit(e) {
  e.preventDefault();
  return superagent.get(`${__API_URL__}/api/flights/${ this.state.from }/${ this.state.to }`)
    .then(res => {
      this.setState({
        flights: [...res.body],
        hasSearched: true,
      });
    }).catch(err => {
      this.setState({
        hasError: true,
      });
    });
}

In this example, when handleSubmit is invoked by the user on the frontend, we use superagent to make a GET request to fetch our data from the backend, and we set hasSearched to true on the state of our component.

And that, y'all, is a full-stack MERN app. 🎉

  Tweet It

🕵 Search Results

🔎 Searching...