NodeJS-React-Redux Tutorial - Part 4: MongoDB and Mongoose

In part 4 of this series we will setup MongoDB for use with our news API

1.7 MongoDB and Mongoose

Next we need a database. The easiest way to do this is to use a cloud hosted database. Head over to mlab.com and signup for an account, and create a new free sandbox database and then create a new database user

With that setup we’ll next install Mongoose, an ORM we will use for MongoDB. We’ll also install dotenv so that we can store our DB URL as an environment variable and have it loaded in.

Run the following in your server directory

npm install mongoose dotenv --save

In the server folder create a file .env, entering the URI for your mLab database along with the username and password you created

PORT = 5000
MONGO_DB_URL = [URI HERE]

Next we need to integrate Mongoose and setup a connection to the db.

...
const mongoose = require('mongoose')

require('dotenv').config()

let app = express();
const PORT = process.env.PORT || 5000;

const dbURL = process.env.MONGO_DB_URL

mongoose.connect(dbURL, function(err){
  if(err){
    console.log('Error connecting to: '+ dbURL)
  }
  else{
    console.log('Connected to: '+ dbURL)
  }
})

app.use(cors());
app.options('*', cors());    
...

Once the server restarts you should see in the console window that it has successfully connected.

Next, we’ll create a model for our news. Make a new folder within the server directory called models and create a file called News.js

const mongoose = require('mongoose');  

const NewsSchema = new mongoose.Schema({  
    title: String,
    teaser: String,
    body: String,
    status: {
        type: Number,
        default: 1
      },
    created: {
        type: Date,
        required: true,
        default: new Date()
    }      
});

mongoose.model('News', NewsSchema);

module.exports = mongoose.model('News');

We’ll also want a controller for our News. Create another folder in server and call it controllers. Within that folder create a new NewsController.js

const News = require('../models/News')

module.exports = {
    find: function(params, callback){
        News.find(params,'_id title teaser', function(err, results){
            if(err){
                callback(err, null);
                return;
            }
            callback(null, results);
        })
    },

    findById: function(id, callback){
        News.findById(id, function(err, results){
            if(err){
                callback(err, null);
                return;
            }
            callback(null, results);
        })
    }
}

This will export 2 functions, 1 for returning all News, one for returning a single news story.

We’ll now alter our News route to use the controller

const express = require('express')
const router = express.Router()
const newsController = require('../controllers/newsController')


router.get('/', function(req, res, next) {

    newsController.find(req.query, function(err, results){
        if(err){
            console.log(err);
            res.json({
                success: 0,
                error: err
            });
            return;
        }
        res.json({
            success: 1,
            data: results
        });
    });
});


router.get('/:id', function(req, res, next){
    const id = req.params.id;

    newsController.findById(id, function(err, result){
    
        if(err){
            console.log(err);
            res.status(500).json({
                success: 0,
                data: result
            });
            return;
        }

        res.status(200).json({
            success: 1,
            data: result
        });
    });
});

module.exports = router

We need to now make a few changes to our client components to use _id instead of id

NewsItemDetail.js

...
NewsItemDetail.propTypes = {
    data: PropTypes.shape({
        _id: PropTypes.string.isRequired,
        title: PropTypes.string.isRequired,
        body: PropTypes.string.isRequired
    })
};
...

NewsItemListing.js

...
            <div>
                <div><Link to={`/news/${this.props.data._id}`}><b>{this.props.data.title}</b></Link></div>
                <div>{this.props.data.teaser}</div>
            </div>
        )
    }
}
NewsItemListing.propTypes = {
    data: PropTypes.shape({
        _id: PropTypes.string.isRequired,
        title: PropTypes.string.isRequired,
        teaser: PropTypes.string.isRequired
    })
};
...

Later on we will change the above to use a slug based on the title but for now we will continue on using the id.

With that done we now have our API returning data from the database back to our client. Currently we have no news however…

Let’s start by add create functionality to our NewsController on the server

NewsController.js

...
module.exports = {

    create: function(params, callback){

        News.create(params, function(err, result){
            if(err){
                callback(err, null);
                return
            }
            callback(null, result);
        });
    },
....    

In our routes/news.js we’ll add a post route and use this new controller create function.

router.post('/', function(req, res, next) {
    newsController.create(req.body, function(err, result){
        if(err){  
            console.log(err);
            res.json({
                success: 0,
                error: err
            })
            return;
        }

        res.json({
            success: 1,
            data: result
        });
    });

We next need to integrate some middleware for parsing our incoming request body. For this we will use body-parser

npm install body-parser --save

In App.js on our server, we’ll then apply the middleware before our news route.

...
const bodyParser = require('body-parser');
...

app.use(cors());
app.options('*', cors());

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use('/', routes);
....

Switching to the client , lets add an action for making the POST request to our API

export function submitNewsStory(data){
    return dispatch => {
        return fetch('/news/', { 
            method: 'POST', 
             headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
              },
            body: JSON.stringify(data), 
            mode: 'cors'})
            .catch( (e) => console.log(e) );
    }    
}

Lets now make a simple news form so that anyone with news can submit a story. In the components/containers folder create the following file called NewsSubmit.js

import React, { Component} from 'react';
import NewsItemDetail from '../presentation/NewsItemDetail';
import { connect } from 'react-redux';
import { submitNewsStory } from '../../actions/actions';
import { withRouter } from "react-router-dom";

class NewsSubmit extends Component {

    constructor(){
        super();

        this.state = {
            submission:{
            }
        };
    }

    componentDidMount(){
        
    }

    updateSubmission(event){
        let updatedSubmission = Object.assign({}, this.state.submission);

        updatedSubmission[event.target.id] = event.target.value;
        this.setState({
            submission: updatedSubmission   
        });
    }

    submitSubmission(){
        this.props.dispatch(submitNewsStory(this.state.submission));    
        this.props.history.push("/");
    }

    render(){

        return (
            <div>
                Title <input onChange={this.updateSubmission.bind(this)} id="title" type="text" placeholder= "Title"/><br/>
                Teaser <input onChange={this.updateSubmission.bind(this)} id="teaser" type="text" placeholder= "Teaser"/><br/>
                Body<br/>
                <textarea onChange={this.updateSubmission.bind(this)} id="body" type="text">

                </textarea><br/>

                <button onClick={this.submitSubmission.bind(this)}>Submit story</button>
            </div>
        )
    }
}

const mapStateToProps = state => {
    return {
    }
}

export default withRouter(connect(mapStateToProps)(NewsSubmit));

This component has a very basic form. Whenever a form element is changed the component will update a state object. When the submit button is hit it will dispatch the action and then redirect to the homepage (You’ll note the use of withRouter in the export statement in order to make this.props.history available)

The final step to make our create news work is to add the new component into our client router

client/src/app.js

...
import NewsSubmit from './components/containers/NewsSubmit';

class App extends Component {
  render() {
    return (
        <Provider store={store}>
            <BrowserRouter>
                <Layout>
                    <Route exact path="/" component={Home} />
                    <Route path="/about" component={About} />
                    <Route path='/news/:id' component={NewsArticle}/>
                    <Route path='/submit' component={NewsSubmit}/>
                </Layout>
...

You should now be able to create news storys.

Next we are going to add the ability for someone to comment on the news story. Before we can do that we are going to need register and login..

[ To Part 5 ] »