DEV Community

Cover image for FullStack setup (Node.js, React.js and MongoDB)
Thiago Pacheco
Thiago Pacheco

Posted on • Updated on

FullStack setup (Node.js, React.js and MongoDB)

Whenever I have to create a new project, I prefer to keep my stack with only one language. So I love use javascript for everything, with Node.js, Express.js, React.js and I really like to use NoSQL databases like MongoDB in this case.

So I decided to share my experience of setting up this environment from scratch.

First, let's create a folder and generate our package.json file for this project.

$ mkdir node-react-starter
$ cd node-react-starter
$ npm init -y
Enter fullscreen mode Exit fullscreen mode

Now, let's install the project dependencies

$ npm install --save express body-parser mongoose
Enter fullscreen mode Exit fullscreen mode

In this project we use Express.js, a very popular framework for Node.js applications.
body-parser is used to parse incoming request bodies in a middleware before your handlers, available under the req.body property.
Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment.

Then, install the development dependencies

$ npm install --save-dev nodemon concurrently
Enter fullscreen mode Exit fullscreen mode

nodemon is a package that runs the node.js application and listen to any file change, updating the entire app.

Concurrently allows us to run multiple npm commands at the same time.

After installing the dependencies, you should get a file like this:

package.json file example

Let's create the project structure

$ mkdir models routes
$ touch index.js
Enter fullscreen mode Exit fullscreen mode

Open the index.js file and add the following code:

//  index.js

const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');

const app = express();

mongoose.Promise = global.Promise;
mongoose.connect(process.env.MONGODB_URI || `mongodb://localhost:27017/node-react-starter`);

app.use(bodyParser.json());

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
  console.log(`app running on port ${PORT}`)
});
Enter fullscreen mode Exit fullscreen mode

After this, you can add a run script inside your package.json file, under scripts:

"server": "nodemon index.js"
Enter fullscreen mode Exit fullscreen mode

At this point, you can run your backend and have a successful connection with the mongodb (MongoDB must be up and running). You can run the script you just created like so:

$ npm run server
Enter fullscreen mode Exit fullscreen mode

Let's initiate our version control to keep track of every change. But first we need to add a .gitignore file in the root of our project with the following content:

node_modules
.idea
Enter fullscreen mode Exit fullscreen mode

Then, we initiate our version control

$ git init
$ git add .
$ git commit -am "first commit"
Enter fullscreen mode Exit fullscreen mode

We successfully created our backend structure, now let's jump to the frontend.

Now, let's create a React app with create-react-app.

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

Now, in the client directory we have to add our dependencies.
Here we are going to use yarn to add this dependencies.

$ cd client
$ yarn add axios
Enter fullscreen mode Exit fullscreen mode

axios is a very popular promise based HTTP client for the browser and node.js.

For react-scripts >= 0.2.3

For the current react version (and any other react-scripts > 0.2.3), you can simply add the following line to your package.json file in the client directory and that will allow you to proxy your front-end requests to the back-end app.

"proxy": "http://localhost:5000"
Enter fullscreen mode Exit fullscreen mode

For react-scripts < 0.2.3

If you are using an older version of react-scripts you might need to add the following configuration to be able to connect the front-end with the back-end:

$ cd client
$ yarn add http-proxy-middleware
Enter fullscreen mode Exit fullscreen mode

http-proxy-middleware is used to create a proxy from our react app to the backend app while on development.

We can now add the config file to setup the proxy to make requests from our frontend to our backend application.
Remember to add this configuration only if you are using an older react version, being react-scripts < 0.2.3.

In the directory /client/src, add the file setupProxy.js with the following content

// /client/src/setupProxy.js

const proxy = require('http-proxy-middleware')

module.exports = function(app) {
    app.use(proxy('/api/*', { target: 'http://localhost:5000' }))
}

Enter fullscreen mode Exit fullscreen mode

In the package.json in the root of the project, let's add the following run scripts:

"client": "npm run start --prefix client",
"server": "nodemon index.js",
"dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"",
"start": "node index.js"
Enter fullscreen mode Exit fullscreen mode

Now your package.json file should look like this:

{
  "name": "node-react-starter",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "client": "npm run start --prefix client",
    "server": "nodemon index.js",
    "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"",
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "express": "^4.17.1",
    "mongoose": "^5.6.3"
  },
  "devDependencies": {
    "concurrently": "^4.1.1",
    "nodemon": "^1.19.1"
  }
}

Enter fullscreen mode Exit fullscreen mode

Now you are able to run the project with the following command:

$ npm run dev
Enter fullscreen mode Exit fullscreen mode

This will run the backend application on port 5000, and the frontend on port 3000.
You should see the react application running on http://localhost:3000

To make our project production ready, we need to add the following lines in our index.js file, right after the app.use(bodyParser.json()) call:

if (process.env.NODE_ENV === 'production') {
  app.use(express.static('client/build'));

  const path = require('path');
  app.get('*', (req,res) => {
      res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'))
  })

}
Enter fullscreen mode Exit fullscreen mode

This will redirect all the requests to our frontend application, unless we specify any route before this code.

Now let's create a simple interaction to see the proxy connection in action

Add the file Product.js inside the directory /models and insert the following code:

// /models/Product.js

const mongoose = require('mongoose');
const {Schema} = mongoose;

const productSchema = new Schema({
    name: String,
    description: String,
})

mongoose.model('products', productSchema);
Enter fullscreen mode Exit fullscreen mode

Let's create a route for our backend API.

Add the file productRoutes.js inside the directory /routes and insert the following code:

// /routes/productRoutes.js
const mongoose = require('mongoose');
const Product = mongoose.model('products');

module.exports = (app) => {

  app.get(`/api/product`, async (req, res) => {
    let products = await Product.find();
    return res.status(200).send(products);
  });

  app.post(`/api/product`, async (req, res) => {
    let product = await Product.create(req.body);
    return res.status(201).send({
      error: false,
      product
    })
  })

  app.put(`/api/product/:id`, async (req, res) => {
    const {id} = req.params;

    let product = await Product.findByIdAndUpdate(id, req.body);

    return res.status(202).send({
      error: false,
      product
    })

  });

  app.delete(`/api/product/:id`, async (req, res) => {
    const {id} = req.params;

    let product = await Product.findByIdAndDelete(id);

    return res.status(202).send({
      error: false,
      product
    })

  })

}
Enter fullscreen mode Exit fullscreen mode

We can now import the models and routes files inside our index.js like so:

// /index.js
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');

// IMPORT MODELS
require('./models/Product');

const app = express();

mongoose.Promise = global.Promise;
mongoose.connect(process.env.MONGODB_URI || `mongodb://localhost:27017/node-react-starter`);

app.use(bodyParser.json());

//IMPORT ROUTES
require('./routes/productRoutes')(app);

if (process.env.NODE_ENV === 'production') {
  app.use(express.static('client/build'));

  const path = require('path');
  app.get('*', (req,res) => {
      res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'))
  })

}

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
  console.log(`app running on port ${PORT}`)
});
Enter fullscreen mode Exit fullscreen mode

Now if we run the project we are able to make requests to our simple product api using the url http://localhost:5000/api/product.
Here we can get, insert, update and delete a product.

Back to the react application, lets add a service to make requests to the backend application.
Inside the folder /client/src create a folder called services and add a file productService.js with the following content:

//  /client/src/services/productService.js

import axios from 'axios';

export default {
  getAll: async () => {
    let res = await axios.get(`/api/product`);
    return res.data || [];
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's edit the App.js file, adding a simple UI that shows a list of products:

// /client/src/App.js

import React, { useState, useEffect } from "react";

// SERVICES
import productService from './services/productService';

function App() {
  const [products, setproducts] = useState(null);

  useEffect(() => {
    if(!products) {
      getProducts();
    }
  })

  const getProducts = async () => {
    let res = await productService.getAll();
    console.log(res);
    setproducts(res);
  }

  const renderProduct = product => {
    return (
      <li key={product._id} className="list__item product">
        <h3 className="product__name">{product.name}</h3>
        <p className="product__description">{product.description}</p>
      </li>
    );
  };

  return (
    <div className="App">
      <ul className="list">
        {(products && products.length > 0) ? (
          products.map(product => renderProduct(product))
        ) : (
          <p>No products found</p>
        )}
      </ul>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

At this point, you can run the application again using the command npm run dev, and you will see the following screen:

Use a HTTP client like Postman or Insomnia to add some products. Make a POST request to http://localhost:5000/api/product with the following JSON content:

{
  "name": "<product name>",
  "description": "<product description here>"
}
Enter fullscreen mode Exit fullscreen mode

Now, you will be able to see a list of products rendered on the screen, like so:

I hope you may find this tutorial useful and in the following days I will continue this tutorial showing how to Dockerize this app.

Also check this next post explaining how to deploy this app to heroku.

If you are interested in working with containers, I also made this post that explains How to dockerize this app and deploy to Heroku.

The source code can be found here

Latest comments (73)

Collapse
 
axyut profile image
achyut koirala

Image description
This is the Deploy logs when it is production ready (it works locally), what do you think the problem is ?

Collapse
 
ingosteinke profile image
Ingo Steinke

Hi all!
First of all, thanks to @pacheco for the tutorial!

One thing I keep wondering, after seeing many full-stack mongoose tutorials, is why do we have to define the same data model and logic twice, one in the backend, and - at least partially - in the front-end?

Probably I am missing something, which I will find out when trying, but I am sure anybody must have bothered to try before, to re-use the back-end data model in the front-end? What happened to the idea of isomorphic apps after all?

I can imagine that either re-using the exact same JavaScript code for BE + FE data models would introduce technical debt, coupling or even unintended downsides concerning front-end business logic, or else it's just not compatible enough and the amount of redundancy does not seem to be critical and will be covered by unit tests + end to end testing, at least if there are tests at all?

Hope somebody will share their thoughts and experience...

Collapse
 
ahmedfarazdev profile image
ahmedfarazdev

I am having an issue whenever I try to access the backend server in the browser it says server might be temporarily down.....Why is that....btw I exactly tried the code you mentioned above....

Collapse
 
pacheco profile image
Thiago Pacheco

Hello there, can you confirm what errors do you get in the backend and frontend logs of your application?

Collapse
 
ryukikikie profile image
Ryuki Kuga • Edited

Just sharing
If you got these error messages below,
(node:48490) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
(node:48490) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version. To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.

Change the code something like this

mongoose.connect(
process.env.MONGOD_URL || mongodb://localhost:27017/fdaksjfafdsfasa,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err) => {
if (err) console.error(err);
else console.log("Connected to the mongodb");
}
);

Collapse
 
hofcsabagit profile image
hofcsabaGit

Hi there,

so I found app.delete(/api/product/:id, async (req, res) => {
const {id} = req.params;
let product = await Product.findByIdAndDelete(id);
return res.status(202).send({
error: false,
product
})
})

that I try to call from postman with:
localhost:5000/api/product/:id

With a raw body of JSON of:
{"name": "myName"}

but it sais:
Cannot DELETE /api/product/

Do you guys know what Im missing?

Thanks in advance!
Csaba

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Csaba,

By the error, it could be something wrong with the definition of this app.delete method.
Does your backend log any error in the terminal when you test this endpoint?

If everything is correct in your code, the error could be the ID you are passing in the URL.
As this is just a simple tutorial and I wanted it to be short, it does not have validations but, in a final application, it should contain a lot of validations on the data we are trying to execute actions.

My suggestion to you is to check if the ID you are passing to the URL http://localhost:5000/api/product/<YOUR-PRODUCT-ID> is a valid ID.

Let me know if this helped you solve the problem and do not hesitate to ask if you have more questions.

Collapse
 
mamanidaniel profile image
daniel

Hi Thiago, thanks for this tuto. i was fighting with the proxy, reading the comments section i realize that in your tuto you put the proxy in the server package.json and not in the package.json of the client, maybe you can fix that for the incoming readers. againg thank you for this amazing tutorial

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Daniel, you are totally right!
I am sorry I made you struggle with that, I just fixed it!

Thank you for letting me know that.

Collapse
 
gunduzcihat profile image
Mustafa • Edited

Very confusing documentation.
For example: dev-to-uploads.s3.amazonaws.com/i/...

Which package.json?

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Mustafa, how are you?

I am sorry if that was not that clear, it was my first article so I am still improving it.
But as I said in this part, it is the package.json located in the root of the project, which means the file related to the backend part.

Let me know if I can help you with anything else.

Collapse
 
gunduzcihat profile image
Mustafa

Sorry if I am being rude.

For example, in your doc, you says process.env.MONGODB_URI or process.env.NODE_ENV but all that give errors "undefined"

so what can I do?

Thread Thread
 
pacheco profile image
Thiago Pacheco

Hi Mustafa,

These variables when working on the localhost will be undefined, but that should not trigger errors because there is a checking on each of them applying another value in case they are undefined.

These are the environment variables that can make the app production-ready. For example, the MONGODB_URI will be used as your production mongo URL and the NODE_ENV will be used to render the correct front-end application in case it is set to production.

They are used in the following tutorials (found at the end of the article) about how to dockerize and deploy this app.

Thread Thread
 
gunduzcihat profile image
Mustafa

Thanks for the reply. I have sent the message you via instagram dm. I need more explanations.

Anyway, you said in your doc:
"Now if we run the project we are able to make requests to our simple product api using the url localhost:5000/api/product.
Here we can get, insert, update and delete a product."

But I tried it with postman but it waits too long. nothing returns.

For instance:

localhost:5000/api/product?name=mu...

it says, "sending request..." and it stucks.

Collapse
 
melonfrappe profile image
melonfrappe

Hi Thiago, This is a best useful and easily guide for beginner like me but I am facing a some problem...

Last year I have done it finished once and it work completely in my macbook but now I do one more on VMware. Everything almost work but it stuck in front-end part.

My app can GET and POST to localhost:5000, data was completely send to database but now it never found "product" from database cause my localhost:3000 always show "No products found" and white blank page...

I think because of client side but I am not sure which code cause this. Could you please adviseme Thiago. Thanks you !

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Melonfrappe!

Can you check in your browser's console and network tab what are the errors you get while trying to fetch the data?
It smells like a proxy error maybe.

Collapse
 
oscarzapi profile image
oscarzapi

Hi Thiago, Thank you so much for this great tutorial. I followed the whole tutorial exactly as you mentioned and at the end when inserting the post requests to include some products... the console was showing this message:
GET localhost:3000/api/product 404 (Not Found)
dispatchXhrRequest @ xhr.js:178 ........

and then I changed in productService.js, axios.get("/api/product) for axios.get(http://localhost:5000/api/product) and the whole app worked!

I have the proxy in the package.json included and everything...but not sure if I missed something..do you know why this happens?Thank you in advance!!!

Collapse
 
pacheco profile image
Thiago Pacheco

Another thing, you should not include the localhost in your axios.get function call.
It should be axios.get("/api/product", ...).

The base URL definition is already set at the beginning of the application when we build the server and define a port, in this case 5000.

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Oscar,

Are you getting this error in the front-end console or is it in an HTTP client?
If this is in your react app, it seems like something is wrong with the proxy config.

Collapse
 
oscarzapi profile image
oscarzapi

It was showing this error inside Chrome Devtools (where localhost:3000).

Thread Thread
 
pacheco profile image
Thiago Pacheco

Are you able to insert data through an HTTP client like Insomnia or postman?

Thread Thread
 
oscarzapi profile image
oscarzapi

I think I found the issue. I had included the sentence "proxy": "localhost:5000" inside the package.json belonging to the server, and not inside the package.json of the client(react app). May be that one the problem Thiago? Because now I´m not getting any errors.

Thread Thread
 
pacheco profile image
Thiago Pacheco

That was it, for sure Oscar.

Let me know if you have any other questions! :)

Collapse
 
yannykyps profile image
yannykyps

Thiago, this is great, thanks. I'm a newbie here and this is exactly what I was looking for as a fullstack setup. I've managed to incorporate this in my first "planner" app. Next step for me is to POST data to the DB through the front-end. Thanks again.

Collapse
 
pacheco profile image
Thiago Pacheco

I am very happy to know that it helped you!
Let me know if you have any questions.

Collapse
 
hilbeert profile image
hil-beer-t

Amazing. Works perfectly. Thx.

Collapse
 
saadaminechouar profile image
saadaminechouar

Hello guy, thanks a lot for your content, i actually have a a project similar to that one and it helped. So my question is there any Dockerize version of this app available yet ?

Collapse
 
pacheco profile image
Thiago Pacheco

Hello,

As we are all in this quarentine, I'm working right now on a tutorial for that. I will try to post it today. :)

Collapse
 
pacheco profile image
Thiago Pacheco

I just made a post about how to dockerize this app and deploy a container to heroku.
dev.to/pacheco/how-to-dockerize-a-...
Check it out! :)

Collapse
 
leosalgueiro profile image
LeoSalgueiro • Edited

Hi! excellent post. i like this configuration and now i'm using it. But i have a problem, i don't know how configure script for debug in vscode. i was trying but with bad results. Could help me with this?

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Leo,

You can create a file launch.json inside the folder .vscode with the following code:

{
"version": "0.2.0",
"configurations": [
{
"name": "Launch via npm",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "npm",
"runtimeArgs": ["run-script", "dev"],
"port": 9229
},
]
}

Then you can just hit F5 and your app might run.

Let me know if that works for you! :)

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Leo,

Sorry, You should also have a new debug script in your package.json file. Add the following script to your package.json:

"debug": "nodemon --nolazy --inspect-brk=9229 index.js",

Also, update the launch.json runtimeArgs to use this new script:

"runtimeArgs": ["run-script", "debug"],

Let me know if that works for you

Collapse
 
leosalgueiro profile image
LeoSalgueiro • Edited

Hi thiago, Thanks for response. i was make that how you tell me but pick a error ECONNREFUSED.

reason: Cannot connect to the target: connect ECONNREFUSED 127.0.0.1:9229.

the .vscode is the same that you give me, and the package.json is the same that the tutorial.
server for mongodb is up, but for the error my targe is wrong.

thi is my index.js

dev-to-uploads.s3.amazonaws.com/i/...

Collapse
 
gkranasinghe profile image
gkranasinghe

[0] [nodemon] app crashed - waiting for file changes before starting...
[1] proxy is not a function

Collapse
 
pacheco profile image
Thiago Pacheco

It seems like you do not have the http-proxy-middleware package installed.

Collapse
 
coffeetub profile image
Jack

What to do if we want to deploy multiple apps? They must share the same database, correct?

Collapse
 
pacheco profile image
Thiago Pacheco • Edited

Hi Jack,

That depends on the project you are working on.
Currently, with almost everything migrating to microservices approach, it is common to have one database for each service/functionality.