MERN stack setup
Part 1 of Fullstack Javascript application with MERN
Simple MERN (Mongo, Express, React, Node) project setup. An ideal Fullstack Javascript environment to build WEB applications. Including tools configuration for debugging, transpiling, minification, bundling, hot reload ….
This setup assumes Node is already configured in your system.
Github: repo
Step1:
Create a project directory and initialize the project with package.json
mkdir mern-setupcd mern-setupnpm init -y
Step2:
Adding the required packages to the project.
KEY Modules
- react
- react-dom
- express
- mongodb
DevDependancies Modules
Babel modules for converting ES6 & JSX to suitable Javascript for all browsers.
- babel-core
- babel-loader
- babel-preset-env
- babel-preset-react
- babel-preset-stage-2
- @hot-loader/react-dom
Webpack modules to bundle compiled Javascript.
- webpack
- webpack-cli
- webpack-node-externals
- webpack-dev-middleware
- webpack-hot-middleware
Watchers for client and server code
- nodemon
- react-hot-loader
npm install react react-dom express mongodb react-hot-loader -savenpm install @hot-loader/react-dom babel-core babel-loader babel-preset-env babel-preset-react babel-preset-stage-2 nodemon webpack webpack-cli webpack-dev-middleware webpack-hot-middleware webpack-node-externals --save-dev
Step3:
Configuring Babel
create a .babelrc file in the project folder and add presets and plugins
{
"presets": [
"env",
"stage-2",
"react"
],
plugins: [
"react-hot-loader/babel"
]
}
Step4:
Configuring Webpack
Create 3 Webpack configurations for bundling Server, client-side and client-side production code.
Step4.1:
Client-Side Webpack configuration for development.
create webpack.config.client.js file in the project folder
const path = require('path');const webpack = require('webpack');const CURRENT_WORKING_DIR = process.cwd();const config = { name: 'browser', mode: 'development', devtool: 'eval-source-map', entry: [ 'react-hot-loader/patch', 'webpack-hot-middleware/client?reload=true', path.join(CURRENT_WORKING_DIR, 'client/index.js'), ], output: { path: path.join(CURRENT_WORKING_DIR, '/dist'), filename: 'bundle.js', publicPath: '/dist/', }, resolve: { alias: { 'react-dom': '@hot-loader/react-dom', }, }, module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, use: ['babel-loader'], }, ], }, plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), ],};module.exports = config;
- mode sets process.env.NODE_ENV to given value, by default the value is set to producition.
- devtool specifies how source maps are generated.
- entry specifies path and filename to start bundeling.
- output specifies path and filename to generate bundled code.
- publicPath specifies path to export all assets.
- module sets regex rule for file extension to be used for transpilation, and folders to be excluded and transpilation tool to be used.
- HotModuleReplacementPlugin enables hot module replacement for react-hot-loader.
- NoEmmitOnErrorsPlugin allows skipping emitting when there are compile errors.
Step4.2
Client-Side Webpack configuration for development.
create webpack.config.client.production.js file in the project folder
const path = require('path');const webpack = require('webpack');const CURRENT_WORKING_DIR = process.cwd();
const config = { mode: 'production', entry: [path.join(CURRENT_WORKING_DIR, 'client/index.js')], output: { path: path.join(CURRENT_WORKING_DIR, '/dist'), filename: 'bundle.js', publicPath: '/dist/', }, module: { rules: [ { test: /\.jsx?$/, exclude: /node_modules/, use: ['babel-loader'], }, ], },};module.exports = config;
Step4.3
Server-Side Webpack configuration.
create webpack.config.server.js file in the project folder
const path = require('path');const nodeExternals = require('webpack-node-externals');const CURRENT_WORKING_DIR = process.cwd();const config = { name: 'server', entry: [path.join(CURRENT_WORKING_DIR, './server/index.js')], target: 'node', output: { path: path.join(CURRENT_WORKING_DIR, '/dist/'), filename: 'server.generated.js', publicPath: '/dist/', libraryTarget: 'commonjs2', }, externals: [nodeExternals()], module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'], }, ], },};module.exports = config;
- mode is not set here explicitly but will be passed as required when running webpack commands.
Step5
Configuring Nodemon
create nodemon.js file in the project folder
{ "verbose": false, "watch": [ "./server" ], "exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js"}
This nodemon configuration will watch for changes in server files during development and execute compile and build commands as necessary.
Step6
Linting and Formatting with ESLint and Prettier
ESLint and prettier help maintain code quality.
Step6.1
Add ESLint & prettier dependencies
npm install --save-dev eslint prettier eslint-config-prettier eslint-plugin-prettier
Ste6.2
Add Commonly used Airbnb ESLint rules with dependencies
npx install-peerdeps --dev eslint-config-airbnb
Step6.3
edit .eslintrc.json file in the project folder
{ "env": { "browser": true, "node": true }, "extends": ["airbnb", "prettier"], "plugins": ["prettier"], "rules": { "prettier/prettier": ["error"], "react/jsx-filename-extension": [ 1, { "extensions": [".js", ".jsx"] } ] }}
Step6.4
Add prettier configuration
create .prettierrc file in the project holder
{ "semi": true, "trailingComma": "all", "singleQuote": true, "printWidth": 70}
Step6.5
Add Husky for linting before pre-commit
npm install -D pretty-quick husky
Configure pre-commit hook in package.json
{
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
}
}
Step7
Template file to serve the React components.
create a template.js file in the project folder.
const template = () => { return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>MERN Setup</title> </head> <body> <div id="root"></div> <script type="text/javascript" src="/dist/bundle.js"></script> </body> </html>`;};module.exports = template;
When the server receives a request to the root URL, the template HTML file will be rendered and div element with id “root” will render the react components.
Step8
Setting up client folder for frontend.
create a subfolder called “client” in the project folder.
create a file called “index.js” in the client folder.
import React from 'react';import { render } from 'react-dom';import App from './app';render(<App />, document.getElementById('root'));
Step9
Create your first app component
create a subfolder named “app” inside the client and add “index.js” file to the “app”.
import React from 'react';import { hot } from 'react-hot-loader/root';const App = () => { return <div>Hello MERN</div>;};export default hot(App);
Step10
Setting up the server folder for backend
create a subfolder called “server” in the project folder.
create a file named “devBundle.js” in the “server” folder.
import webpack from 'webpack'import webpackMiddleware from 'webpack-dev-middleware'import webpackHotMiddleware from 'webpack-hot-middleware'import webpackConfig from './../webpack.config.client.js'const compile = (app) => { if(process.env.NODE_ENV == "development"){ const compiler = webpack(webpackConfig) const middleware = webpackMiddleware(compiler, { publicPath: webpackConfig.output.publicPath }) app.use(middleware) app.use(webpackHotMiddleware(compiler)) }}export default {compile}
Bundling React code with webpack configuration during development mode.
We use webpack to compile client code when the server is running. Compile method uses Express app to configure webpack middleware to compile, bundle and serve code, and enables hot reloading in development mode.
Step11
Setting up the server to serve static files and configuring MongoDB client.
create a subfolder called “server” in the project folder.
create a file called “index.js” in the client folder.
import path from 'path'import express from 'express'import { MongoClient } from 'mongodb'import template from '../template'import devBundle from './devBundle'const app = express()if (process.env.NODE_ENV === 'development') { devBundle.compile(app)}const CURRENT_WORKING_DIR = process.cwd()app.use('/dist', express.static(path.join(CURRENT_WORKING_DIR, 'dist')))app.get('/', (req, res) => { res.status(200).send(template())})let port = process.env.PORT || 3000app.listen(port, function onStart(err) { if (err) { console.log(err) } console.info('Server started on port %s.', port)})try { const url = process.env.MONGODB_URI || 'mongodb://localhost:27017/mernSetup' MongoClient.connect(url, { useNewUrlParser: true }, (err, db) => { if (err) { return } console.log("Connected successfully to mongodb server") db.close() })} catch (e) {
console.log(e)
}