Skip to content

Commit

Permalink
Make the CRUD operations update real-time
Browse files Browse the repository at this point in the history
  • Loading branch information
cefjoeii committed Jul 24, 2017
1 parent 8f94ad2 commit 3486046
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 43 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# MERN CRUD Starter Kit
# MERN CRUD

A Create, Read, Update, and Delete starter kit using MongoDB, Express.js, React.js, and Node.js. REST API was implemented on the back-end. Semantic UI React was used for the UI.
A simple records system using MongoDB, Express.js, React.js, and Node.js with real-time Create, Read, Update, and Delete operations using Socket.io. REST API was implemented on the back-end. Semantic UI React was used for the UI.

Make sure MongoDB service is running.
*Make sure MongoDB service is running.*

<br>

For the **back-end**, install the dependencies once.
```
npm install
```
Run the *main server*. It will listen on port 3000.
Run the *main server*. It listens on port 3000.
```
node server
```
Expand All @@ -23,12 +23,12 @@ For the **front-end**, go to *react-src* folder via the terminal.
cd react-src
```

Install the node packages required by React once.
Install the dependencies required by React once.
```
npm install
```

Run the *development server* for React. It will listen on port 4200.
Run the *development server* for React. It listens on port 4200.
```
npm start
```
Expand All @@ -38,18 +38,23 @@ To make a production build, simply run on *react-src* folder.
npm run build
```

It will create a folder named *public* on the root directory. This is where the production-ready front-end of the web application will reside. It can now be directly viewed through the *main server* without running the React development server.
It creates a folder named *public* on the root directory. This is where the production-ready front-end of the web application resides. It can now be directly viewed through the *main server* without running the React development server.

## To Do

- [x] Create
- [x] Read
- [x] Update
- [x] Delete
- [x] Real-time broadcast using Socket.io

## Future Plans

* Search
* Front-end validation; Pure back-end validation is expensive
* Real-time broadcast using socket.io
* Routing / redirecting / whatever
* Learn Redux
* Learn creating tests

## License
* [MIT](LICENSE)
2 changes: 1 addition & 1 deletion config/db.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Separate to easily change when deploying
// Change the MongoDB connection string when deploying

module.exports = {
db: 'mongodb://localhost/mern-crud'
Expand Down
6 changes: 4 additions & 2 deletions models/user.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const mongoose = require('mongoose');
const unique = require('mongoose-unique-validator');
const validate = require('mongoose-validator');

const config = require('../config/db');

const nameValidator = [
Expand All @@ -24,13 +25,14 @@ const emailValidator = [
];

const ageValidator = [
// TODO: Make some validators here...
// TODO: Make some validations here...
];

const genderValidator = [
// TODO: Make some validators here...
// TODO: Make some validations here...
];

// Define the database model
const UserSchema = new mongoose.Schema({
name: {
type: String,
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "mern-crud",
"version": "0.1.0",
"description": "A CRUD starter kit using MongoDB, Express.js, React.js, and Node.js",
"main": "app.js",
"description": "A simple records system using MongoDB, Express.js, React.js, and Node.js with real-time CRUD operations using Socket.io",
"main": "server.js",
"scripts": {
"start": "nodemon server",
"test": "echo \"Error: no test specified\" && exit 1"
Expand All @@ -25,6 +25,7 @@
"mongoose": "4.10.8",
"mongoose-unique-validator": "^1.0.5",
"mongoose-validator": "^1.3.2",
"socket.io": "^2.0.3",
"string-capitalize-name": "^1.0.3"
}
}
1 change: 1 addition & 0 deletions react-src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"react-error-overlay": "^1.0.9",
"semantic-ui-css": "^2.2.11",
"semantic-ui-react": "^0.71.1",
"socket.io-client": "^2.0.3",
"style-loader": "0.18.2",
"sw-precache-webpack-plugin": "0.11.3",
"url-loader": "0.5.9",
Expand Down
4 changes: 4 additions & 0 deletions react-src/src/components/App/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ a.social-link:hover {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

#online {
margin-left: 10px;
}
46 changes: 37 additions & 9 deletions react-src/src/components/App/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { Container } from 'semantic-ui-react';
import axios from 'axios';
import io from 'socket.io-client';

import TableUser from '../TableUser/TableUser';
import ModalUser from '../ModalUser/ModalUser';
Expand All @@ -13,24 +14,41 @@ class App extends Component {
// Change this when deploying.
server = 'http://localhost:3000';

// Leave the parameter empty when deploying
socket = io.connect(this.server);

constructor() {
super();

this.state = { users: [] }
this.state = {
users: [],
online: 0
}

this.fetchUsers = this.fetchUsers.bind(this);
this.handleUserAdded = this.handleUserAdded.bind(this);
this.handleUserUpdated = this.handleUserUpdated.bind(this);
this.handleUserDeleted = this.handleUserDeleted.bind(this);
}

// Fetch data from the back-end
// Place socket.io code inside here
componentDidMount() {
this.fetchUsers();
this.socket.on('visitor enters', data => this.setState({ online: data }));
this.socket.on('visitor exits', data => this.setState({ online: data }));
this.socket.on('add', data => this.handleUserAdded(data));
this.socket.on('update', data => this.handleUserUpdated(data));
this.socket.on('delete', data => this.handleUserDeleted(data));
}

// Fetch data from the back-end
fetchUsers() {
axios.get(`${this.server}/api/users/`)
.then((response) => {
this.setState({ users: response.data });
})
.catch((error) => {
console.log(error);
.catch((err) => {
console.log(err);
});
}

Expand All @@ -48,28 +66,35 @@ class App extends Component {
users[i].email = user.email;
users[i].age = user.age;
users[i].gender = user.gender;
break; //Stop this loop, we found it!
break; // Stop this loop, we found it!
}
}
this.setState({ users: users });
}

handleUserDeleted(user) {
let users = this.state.users.slice();
users = users.filter(function(u) { return u._id !== user._id; });
users = users.filter(u => { return u._id !== user._id; });
this.setState({ users: users });
}

render() {

let online = this.state.online;
let verb = (online <= 1) ? 'is' : 'are'; // linking verb, if you'd prefer
let noun = (online <= 1) ? 'person' : 'people';

return (
<div>
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1>MERN CRUD Starter Kit</h1>
<p>A Create, Read, Update, and Delete starter kit using MongoDB, Express.js, React.js, and Node.js</p>
<h1>MERN CRUD</h1>
<p>A simple records system using MongoDB, Express.js, React.js, and Node.js with real-time Create, Read, Update, and Delete using Socket.io.</p>
<p>REST API was implemented on the back-end. Semantic UI React was used for the UI.</p>
<p><a className="social-link" href="https://github.com/cefjoeii" target="_blank" rel="noopener noreferrer">GitHub</a> &bull; <a className="social-link" href="https://linkedin.com/in/cefjoeii" target="_blank" rel="noopener noreferrer">LinkedIn</a> &bull; <a className="social-link" href="https://twitter.com/cefjoeii" target="_blank" rel="noopener noreferrer">Twitter</a></p>
<p>
<a className="social-link" href="https://github.com/cefjoeii" target="_blank" rel="noopener noreferrer">GitHub</a> &bull; <a className="social-link" href="https://linkedin.com/in/cefjoeii" target="_blank" rel="noopener noreferrer">LinkedIn</a> &bull; <a className="social-link" href="https://twitter.com/cefjoeii" target="_blank" rel="noopener noreferrer">Twitter</a>
</p>
</div>
</div>
<Container>
Expand All @@ -80,12 +105,15 @@ class App extends Component {
buttonColor='green'
onUserAdded={this.handleUserAdded}
server={this.server}
socket={this.socket}
/>
<em id='online'>{`${online} ${noun} ${verb} online.`}</em>
<TableUser
onUserUpdated={this.handleUserUpdated}
onUserDeleted={this.handleUserDeleted}
users={this.state.users}
server={this.server}
socket={this.socket}
/>
</Container>
<br/>
Expand Down
20 changes: 13 additions & 7 deletions react-src/src/components/FormUser/FormUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class FormUser extends Component {
}

componentWillMount() {
// Fill in the form with the appropriate data if user id is provided
if (this.props.userID) {
axios.get(`${this.props.server}/api/users/?id=${this.props.userID}`)
.then((response) => {
Expand All @@ -38,8 +39,8 @@ class FormUser extends Component {
gender: response.data.gender,
});
})
.catch((error) => {
console.log(error);
.catch((err) => {
console.log(err);
});
}
}
Expand All @@ -57,23 +58,26 @@ class FormUser extends Component {
}

handleSubmit(e) {
// Prevent browser refresh
e.preventDefault();

const newUser = {
const user = {
name: this.state.name,
email: this.state.email,
age: this.state.age,
gender: this.state.gender
}

// Acknowledge that if the user id is provided, we're updating via PUT
// Otherwise, we're creating a new data via POST
const method = this.props.userID ? 'put' : 'post';
const params = this.props.userID ? this.props.userID : '';

axios({
method: method,
responseType: 'json',
url: `${this.props.server}/api/users/${params}`,
data: newUser
data: user
})
.then((response) => {
this.setState({
Expand All @@ -89,9 +93,11 @@ class FormUser extends Component {
gender: ''
});
this.props.onUserAdded(response.data.result);
this.props.socket.emit('add', response.data.result);
}
else {
this.props.onUserUpdated(response.data.result);
this.props.socket.emit('update', response.data.result);
}

})
Expand Down Expand Up @@ -131,7 +137,7 @@ class FormUser extends Component {
/>
<Form.Input
label='Email'
type='text'
type='email'
placeholder='[email protected]'
name='email'
value={this.state.email}
Expand All @@ -140,7 +146,7 @@ class FormUser extends Component {
<Form.Group widths='equal'>
<Form.Input
label='Age'
type='text'
type='number'
placeholder='18'
min={0}
max={130}
Expand Down Expand Up @@ -170,7 +176,7 @@ class FormUser extends Component {
content={formErrorMessage}
/>
<Button color={this.props.buttonColor} floated='right'>{this.props.buttonSubmitTitle}</Button>
<br /><br /> {/* Yikes! */}
<br /><br /> {/* Yikes! Deal with Semantic UI React! */}
</Form>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class ModalConfirmDelete extends Component {
this.handleSubmit = this.handleSubmit.bind(this);
}

handleOpen = (e) => this.setState({ modalOpen: true });
handleClose = (e) => this.setState({ modalOpen: false });
handleOpen = e => this.setState({ modalOpen: true });
handleClose = e => this.setState({ modalOpen: false });

handleSubmit(e) {

Expand All @@ -31,6 +31,7 @@ class ModalConfirmDelete extends Component {
.then((response) => {
this.handleClose();
this.props.onUserDeleted(response.data.result);
this.props.socket.emit('delete', response.data.result);
})
.catch((err) => {
this.handleClose();
Expand Down
1 change: 1 addition & 0 deletions react-src/src/components/ModalUser/ModalUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ModalUser extends Component {
onUserAdded={this.props.onUserAdded}
onUserUpdated={this.props.onUserUpdated}
server={this.props.server}
socket={this.props.socket}
/>
</Modal.Content>
</Modal>
Expand Down
4 changes: 3 additions & 1 deletion react-src/src/components/TableUser/TableUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class TableUser extends Component {
userID={user._id}
onUserUpdated={this.props.onUserUpdated}
server={this.props.server}
socket={this.props.socket}
/>
<ModalConfirmDelete
headerTitle='Delete User'
Expand All @@ -33,12 +34,13 @@ class TableUser extends Component {
user={user}
onUserDeleted={this.props.onUserDeleted}
server={this.props.server}
socket={this.props.socket}
/>
</Table.Cell>
</Table.Row>
);

// Make the latest new user appear on top of the list
// Make every new user appear on top of the list
users = [...users].reverse();

return (
Expand Down
Loading

0 comments on commit 3486046

Please sign in to comment.