erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
12.01k stars 2.5k forks source link

Connecting to MongoDb #1231

Closed massimopibiri closed 8 years ago

massimopibiri commented 8 years ago

Hi, First of all, thank you for the fantastic job! I'm not sure this is the good place to ask about. Maybe you don't want to talk too much about the server, to avoid making things too complicated. The proble: I'm trying to build my application on your starter and I got some problems connecting it with MongoDb. I'm not an expert, and your api is way more complex of the MongoDb tutorials we find on web. I don't know if I have to connect it all in api.js, or server.js or...

I set up the MongoDb and it works fine. I already charged some data in it.

Then I require it in api.js: var mongo = require('mongodb'); var monk = require('monk'); var db = monk('localhost:27017/nodetest1');

Then I have to make it accessible: app.use(function(req,res,next){ req.db = db; next(); });

...and then, I get in trouble. Because I should pull the data from mongo and display it, but I don't even know how and where to put the following lines....

router.get('/userlist', function(req, res) { var db = req.db; var collection = db.get('usercollection'); collection.find({},{},function(e,docs){ res.render('userlist', { "userlist" : docs }); }); });

Does anybody have an idea how to implement MongoDb? If you can give an exemple, that would be awsome!!!

jaraquistain commented 8 years ago

@sdruipeng and @massimopibiri if you wrap your code samples with "```javascript" and then another three backticks after the code it will format it correctly.

Just a bit of feedback unrelated to the issue. Sometimes its helpful in better communicating problems/solutions.

like this:

router.get('/userlist', function(req, res) {
  var db = req.db;
  var collection = db.get('usercollection');
  collection.find({},{},function(e,docs){
    res.render('userlist', {
      "userlist" : docs
    });
  });
});
JoeGrasso commented 8 years ago

@massimopibiri why are you getting from the router and not the reducer?

Here's what I did to make this project work with a database in the case of the widgets:

1. API

index.actions.api.js

import * as auth from './auth/auth-credential.actions.api.js';
import * as session from './session/session.actions.api';
import * as survey from './survey/index';
import * as user from './user/user.actions.api';
import * as widget from './widget/widget.actions.api';

function seedDatabase() {
  user.seed();
  widget.seed();
}

module.exports = {
  seedDatabase,
  auth,
  session,
  survey,
  user,
  widget
};

widgets.actions.api.js

import {WidgetModel} from '../../models';
const data = require('../../seed/widgets.seed.api').getData();

function seedTable() {
  let results = [];
  return new Promise((resolve, reject) => {
    WidgetModel.find({}, (err, collection) => {
      if (collection.length === 0) {
        data.map(item => {
          WidgetModel.create(item);
          results.push(item);
        });
        console.log('Widget table seeded');
      } else {
        results = collection;
      }
      if (err) reject(err);
      else resolve(results);
    })
  });
}

module.exports = {

  seed: () => {
    seedTable();
  },

  update: (req) => {
    return new Promise((resolve, reject) => {
      const item = req.body;

      // write to database
      WidgetModel.update({_id: item._id}, item).then(result => {
        console.log(result);
        resolve(item); // This returns the updated widget instead of result
      }, (err) => {
        console.log(err);
        reject(err);
      });
    });
  },

  getAll: (req, params) => {
    const query = params[0] ? {_id: params[0]} : {};
    const query2 = params[1] ? params[1] : '';
    return new Promise((resolve, reject) => {
      // make async call to database
      resolve(seedTable());
    }, (err) => {
      reject(err);
    })
  }
};

2. Reducer

const LOAD = 'widgets/LOAD';
const LOAD_SUCCESS = 'widgets/LOAD_SUCCESS';
const LOAD_FAIL = 'widgets/LOAD_FAIL';
const EDIT_START = 'widgets/EDIT_START';
const EDIT_STOP = 'widgets/EDIT_STOP';
const DELETE_START = 'widgets/DELETE_START';
const DELETE_STOP = 'widgets/DELETE_STOP';
const CREATE_START = 'widgets/CREATE_START';
const CREATE_STOP = 'widgets/CREATE_STOP';
const SAVE = 'widgets/SAVE';
const SAVE_SUCCESS = 'widgets/SAVE_SUCCESS';
const SAVE_FAIL = 'widgets/SAVE_FAIL';

const initialState = {
  loaded: false,
  editing: {},
  saveError: {}
};

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case LOAD:
      return {
        ...state,
        loading: true
      };
    case LOAD_SUCCESS:
      return {
        ...state,
        loading: false,
        loaded: true,
        data: action.result,
        error: null
      };
    case LOAD_FAIL:
      return {
        ...state,
        loading: false,
        loaded: false,
        data: null,
        error: action.error
      };
    case CREATE_START:
      return {
        ...state,
        creating: {
          ...state.creating
        }
      };
    case CREATE_STOP:
      return {
        ...state,
        creating: {
          ...state.creating
        }
      };
    case EDIT_START:
      return {
        ...state,
        editing: {
          ...state.editing,
          [action.idx]: true
        }
      };
    case EDIT_STOP:
      return {
        ...state,
        editing: {
          ...state.editing,
          [action.idx]: false
        }
      };
    case SAVE:
      return state; // 'saving' flag handled by redux-form
    case SAVE_SUCCESS:
      const data = [...state.data];
      data[action.result.idx - 1] = action.result;
      return {
        ...state,
        data: data,
        editing: {
          ...state.editing,
          [action.idx]: false
        },
        saveError: {
          ...state.saveError,
          [action.idx]: null
        }
      };
    case SAVE_FAIL:
      return typeof action.error === 'string' ? {
        ...state,
        saveError: {
          ...state.saveError,
          [action.idx]: action.error
        }
      } : state;
    default:
      return state;
  }
}

export function isLoaded(globalState) {
  return globalState.widgets && globalState.widgets.loaded;
}

// Create url to use as the api action
export function getAll(params) {
  let url;
  if (params.id) {
    url = '/widget/getAll/' + params.id + '/edit';
  } else {
    url = '/widget/getAll';
  }

  // console.log('widgets97-url', url);
  return {
    types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
    promise: (client) => client.get(url) // params not used, just shown as demonstration
  };
}

export function save(widget) {
  return {
    types: [SAVE, SAVE_SUCCESS, SAVE_FAIL],
    idx: widget.idx,
    promise: (client) => client.post('/widget/update', {
      data: widget
    })
  };
}

export function createStart() {
  return { type: CREATE_START };
}

export function createStop() {
  return { type: CREATE_STOP };
}

export function deleteStart(idx) {
  return { type: DELETE_START, idx };
}

export function deleteStop(idx) {
  return { type: DELETE_STOP, idx };
}

export function editStart(idx) {
  return { type: EDIT_START, idx };
}

export function editStop(idx) {
  return { type: EDIT_STOP, idx };
}

3. Container

import React, {Component, PropTypes} from 'react';
import Helmet from 'react-helmet';
import {connect} from 'react-redux';
import * as widgetActions from '../../../shared/redux/reducers/widget/widget.reducer.shared';
import {isLoaded, load as loadWidgets} from '../../../shared/redux/reducers/widget/widget.reducer.shared';
import {initializeWithKey} from 'redux-form';
import { WidgetForm } from '../../components';
import { asyncConnect } from 'redux-async-connect';

@asyncConnect([{
  deferred: true,
  promise: ({store: {dispatch, getState}}) => {
    if (!isLoaded(getState())) {
      return dispatch(loadWidgets());
    }
  }
}])
@connect(
  state => ({
    widgets: state.widgets.data,
    editing: state.widgets.editing,
    error: state.widgets.error,
    loading: state.widgets.loading
  }),
  {...widgetActions, initializeWithKey })
export default class Widgets extends Component {
  static propTypes = {
    widgets: PropTypes.array,
    error: PropTypes.string,
    loading: PropTypes.bool,
    initializeWithKey: PropTypes.func.isRequired,
    editing: PropTypes.object.isRequired,
    load: PropTypes.func.isRequired,
    editStart: PropTypes.func.isRequired
  };

  render() {
    const handleEdit = (widget) => {
      const {editStart} = this.props; // eslint-disable-line no-shadow
      return () => editStart(String(widget.id));
    };
    const {widgets, error, editing, loading, load} = this.props;
    let refreshClassName = 'fa fa-refresh';
    if (loading) {
      refreshClassName += ' fa-spin';
    }
    const styles = require('./Widgets.scss');
    return (
      <div className={styles.widgets + ' container'}>
        <h1>
          Widgets
          <button className={styles.refreshBtn + ' btn btn-success'} onClick={load}>
            <i className={refreshClassName}/> {' '} Reload Widgets
          </button>
        </h1>
        <Helmet title="Widgets"/>
        <p>
          If you hit refresh on your browser, the data loading will take place on the server before the page is returned.
          If you navigated here from another page, the data was fetched from the client after the route transition.
          This uses the decorator method <code>@asyncConnect</code> with the <code>deferred: true</code> flag. To block
          a route transition until some data is loaded, remove the <code>deffered: true</code> flag.
          To always render before loading data, even on the server, use <code>componentDidMount</code>.
        </p>
        <p>
          This widgets are stored in your session, so feel free to edit it and refresh.
        </p>
        {error &&
        <div className="alert alert-danger" role="alert">
          <span className="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
          {' '}
          {error}
        </div>}
        {widgets && widgets.length &&
        <table className="table table-striped">
          <thead>
          <tr>
            <th className={styles.idCol}>ID</th>
            <th className={styles.colorCol}>Color</th>
            <th className={styles.sprocketsCol}>Sprockets</th>
            <th className={styles.ownerCol}>Owner</th>
            <th className={styles.buttonCol}></th>
          </tr>
          </thead>
          <tbody>
          {
            widgets.map((widget) => editing[widget.id] ?
              <WidgetForm formKey={String(widget.id)} key={String(widget.id)} initialValues={widget}/> :
              <tr key={widget.id}>
                <td className={styles.idCol}>{widget.id}</td>
                <td className={styles.colorCol}>{widget.color}</td>
                <td className={styles.sprocketsCol}>{widget.sprocketCount}</td>
                <td className={styles.ownerCol}>{widget.owner}</td>
                <td className={styles.buttonCol}>
                  <button className="btn btn-primary" onClick={handleEdit(widget)}>
                    <i className="fa fa-pencil"/> Edit
                  </button>
                </td>
              </tr>)
          }
          </tbody>
        </table>}
      </div>
    );
  }
}
hengbenkeji commented 8 years ago

@jaraquistain thanks. I came back to this starter after being tortured by Angular2 Observables for about 2 weeks. I feel much better after acquiring a pure front end adapted from this repo.

I have forked an OAuth2 Server from http://scottksmith.com/blog/2014/05/29/beer-locker-building-a-restful-api-with-node-passport/.

Or you can find in my forked repo a working example of OAuth2 Express server (dubbed Beerlocker Example) based on Mongodb.

You can replace the API server in this repo with the Beerlocker example. Thus, you have a stand alone working OAuth2 server which has access to Mongodb via mongoose.

Setting proxy to the API server is concerned with safety issues. Having access to a proxy server is different from a stand alone server. Refer to this link for details. https://www.youtube.com/watch?v=cUWcZ4FzgmI

In a nutshell, the architecture is divided into three tiers:

  1. Front End
  2. OAuth2 Express Server
  3. Mongodb

I have used this structure in one of my projects. By the way, I use Firebase static hosting, therefore, I removed some code to get a pure front end framework. As users grow, I would buy my own servers.

massimopibiri commented 8 years ago

Thank you guys! That's really generous.

massimopibiri commented 7 years ago

Hi guys, I post the solution I found without changing api. I posted it also in stackoverflow at this address:

http://stackoverflow.com/questions/38426066/how-to-implement-express-with-mongodb/38706939#38706939

and I post it here also. I hope it will save time for others. For the moment, I just achieved to write in the Mongo Database. But a lot of work reste to do. And I'm not sure the way I use it is the best for the performance. So feel free to improve it and developpe a complete answer.

I'll write the complete procedure, just to be sure.

Install MongoDb on your computer. It will install Mongo in C:\ I did it twice, the second time it was installed in Porgrams.

Create a new folder called "data" inside the folder called api.

Then navigate with the terminal inside the MongoDb folder and find bin. From there, digit the path to the data folder:

mongod --dbpath c:\nameofthefolderproject\api\data

Mongo initialize the data store (it takes a moment), starts and waits for connection.

Navigate with the terminal in the root of your project and installe mongoose:

npm install mongoose

So, here I come with a solution. I'll try to leave an answer as the one I would like to find.

For the moment, I just achieved to write in the Mongo Database. But a lot of work reste to do. And I'm not sure the way I use it is the best for the performance. So feel free to improve it and developpe a complete answer.

So, here how I did to use MongoDb with express and socket.io. I'm building my site from the boilerplate of Erik Ras

https://github.com/erikras/react-redux-universal-hot-example Install MongoDb on your computer. It will install Mongo in C:\ I did it twice, the second time it was installed in Porgrams.

Create a new folder called "data" inside the folder called api.

Then navigate with the terminal inside the MongoDb folder and find bin. From there, digit the path to the data folder:

mongod --dbpath c:\nameofthefolderproject\api\data Mongo initialize the data store (it takes a moment), starts and waits for connection.

Navigate with the terminal in the root of your project and installe mongoose:

npm install mongoose Then open the file nameofthefolderproject\api\api.js There import mongoose:

import mongoose from 'mongoose';

and add right after the imports:

var mongoURI = "mongodb://localhost/nameofyourproject";
var db = mongoose.connect(mongoURI, function(err){
  if(err){
    console.log(err);
  }else{
    console.log('Finally connected!!!');
  }
});

You should recive the message in the terminal.

Create a folder in the api folder called: nameofthefolderproject\api\models

Inside create the file nameofthefolderproject\api\models\members.js

Create a mongoose schema. Look at the doc to understand it if you don't know it.

var mongoose = require('mongoose');

var membersSchema = mongoose.Schema({
    pseudo:{
        type: String,
        required: true
    },
    email:{
        type: String,
        required: true
    },
    date:{
        type: Date,
        default: Date.now
    }
});
var Members = module.exports = mongoose.model('members', membersSchema);

Create the file: nameofthefolderproject\api\models\index.js and write the following. Here you will collect all the models exporting everything ready to use.

export members from './members'; When the api calls the action, pass the models to the function.

app.use(bodyParser.json());

app.use((req, res) => {
  const splittedUrlPath = req.url.split('?')[0].split('/').slice(1);
  const {action, params} = mapUrl(actions, splittedUrlPath);
  // it calls the right action passing also the mongoose schemas (models)
  if (action) {
action(req, params, models) etc etc...

Now go in the action called by the server. In my case: nameofthefolderproject\api\actions\addMember You can now use the model to register. In my case:

export default function addMember(req, err, models) {
   // this is the function called by the api. I passed all the models of mongoose.
  return new Promise((resolve, reject) => {
    // charge the data received from the form in a variable
    var data = req.body;
    // extract the mongoose model I interested about. 
    var Members = models.members;
    // initialise the mongoose schema and connect the data received to the schema
    var newMember = new Members({pseudo:data.pseudo, email:data.email});
    // In this way I can register the data in mongodb data store
    newMember.save(function(err, data){
        // verify if there is an error, and confirm eventually the success
         if(err){
            throw err;
            reject();
        }
        resolve();
    });
  });
}

If I'll have the time in next days I'll develop the rest and I post it.

Enjoy.