In this project, we will be creating a chat application that will use sessions and custom middleware.
- Fork and clone this repository.
cd
into the project.- Run
npm install
.
In this step, we'll use npm
to install express
and dotenv
and set up our server file.
- Run
npm install express dotenv
. - Create a folder called
server
. Within theserver
folder, create theindex.js
file. - Create a
.env
file in the root folder of your project. Add the propertySERVER_PORT
to your.env
file and assign it the value of3005
- Set up your server file using
express
,express.json
middleware, and listening on a port.- Don't forget to configure
dotenv
to use with your session secret and include your.env
file in your.gitignore
.
- Don't forget to configure
server/index.js
require("dotenv").config();
const express = require("express");
let { SERVER_PORT } = process.env;
const app = express();
app.use(express.json());
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
.env
SERVER_PORT = 3005
In this step, we will set up the proxy
so that all non text/html requests are forwarded on to our node/express server. We'll also set up the main
property so you can easily start your server with nodemon
.
- Open your
package.json
file. - Add the following line.
"proxy": "http://localhost:3005"
- Add the following line.
"main": "server/index.js"
package.json
...
"main": "./server/index.js",
"proxy": "http://localhost:3005"
}
In this step, we will set up needed endpoints and create a controller file.
- Create a file called
messagesCtrl.js
in theserver
folder. - Create a variable called
allMessages
, which is an empty array. - Export an object using
module.exports
.- Add a method called
getAllMessages
that responds with theallMessages
variable.
- Add a method called
- Create a GET endpoint with a path of
/api/messages
and use thegetAllMessages
method as the callback.- Don't forget to require the controller file.
- Add another method to the controller file called
createMessage
.- A username and message will be sent in the body of the request. Create a new message object with the
username
andmessage
properties. Push the new message object into theallMessages
array. - Respond with the updated
allMessages
array.
- A username and message will be sent in the body of the request. Create a new message object with the
- Create a POST endpoint with a path of
/api/message
and use thecreateMessage
method as the callback.
index.js
require("dotenv").config();
const express = require("express");
const messagesCtrl = require("./messagesCtrl");
let { SERVER_PORT } = process.env;
const app = express();
app.use(express.json());
app.get("/api/messages", messagesCtrl.getAllMessages);
app.post("/api/message", messagesCtrl.createMessage);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
messagesCtrl.js
let allMessages = [];
module.exports = {
getAllMessages: (req, res) => {
res.status(200).send(allMessages);
},
createMessage: (req, res) => {
const { username, message } = req.body;
let newMessage = {
username,
message
};
allMessages.push(newMessage);
res.status(200).send(allMessages);
}
};
In this step, we will start making HTTP requests, from our react app to our node/express server, so that we can
- get all messages and display them
- create a new message
- Open
App.js
and importaxios
at the top of the file. - Add the
componentDidMount
lifecycle method and make a GET request to your node/express api- path:
'/api/messages'
- Set state with the response. Update the
allMessages
property on state.
- path:
- Find the
createMessage
method and make a post request. Sendthis.state.username
andthis.state.message
in the body of the request. Useusername
andmessage
property names.- path:
'/api/message'
- body:
{username: this.state.username, message: this.state.message}
- path:
- Set state with the response (which will be the updated array messages from the server)
- Update the
allMessages
property.
- Update the
App.js
...
componentDidMount() {
axios.get('/api/messages').then(res => {
this.setState({ allMessages: res.data });
});
}
createMessage() {
axios
.post("/api/message", {
username: this.state.username,
message: this.state.message
})
.then(res => {
this.setState({
allMessages: res.data
});
});
}
...
In this step, we will set up sessions using the express-session
library. By using sessions, we will be able to keep track of a message history for each user on our app.
At this point, you should have a working app where you can save your username and send messages.
- Run
npm i express-session
; - This library is middleware. We need to configure sessions using the built-in express method
app.use()
; - At the top of
index.js
, require in the libraryconst session = require('express-session');
- In the
.env
file, add a property calledSESSION_SECRET
with an associated value. In theindex.js
file, destructure this value from theprocess.env
object.
- Configure this top level middleware like this:
app.use(session({ secret: SESSION_SECRET, resave: false, saveUninitialized: false }))
- secret: The session secret will add a basic layer of security.
- resave: Forces the session to be saved back to the session store, even if the session was never modified during the request (info from docs).
- saveUninitialized: Forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified (info from docs).
index.js
require("dotenv").config();
const express = require("express");
const messagesCtrl = require("./messagesCtrl");
const session = require("express-session");
let { SERVER_PORT, SESSION_SECRET } = process.env;
const app = express();
app.use(express.json());
app.use(
session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false
})
);
...
.env
SERVER_PORT = 3005
SESSION_SECRET = jfdfjkdslajfdsksuperdupersecretfdjskalfjdsaweifj
In this step, we will use sessions to create a message history.
-
In the
messagesCtrl.js
file, find thecreateMessage
method. We are currently taking all messages that are sent to the server and storing them in theallMessages
array. In addition to this, we will use the user's current session to store all the messages from the user. -
To access session data, just use the property
req.session
.req.session
is an object that we can use to store whatever we want. In this case, we want to add a property calledhistory
that will be an array.- NOTE: Remember, we have access to the
req.session
object because we are using theexpress-session
library.
- NOTE: Remember, we have access to the
-
We need to initialize the
req.session.history
property if it doesn't already exists on thereq.session
object. Then push the new message object into thereq.session.history
array.if (req.session.history) { req.session.history.push(newMessage); } else { req.session.history = []; req.session.history.push(newMessage); }
messagesCtrl.js
let allMessages = [];
module.exports = {
getAllMessages: (req, res) => {
res.status(200).send(allMessages);
},
createMessage: (req, res) => {
const { username, message } = req.body;
let newMessage = {
username,
message
};
allMessages.push(newMessage);
if (req.session.history) {
req.session.history.push(newMessage);
} else {
req.session.history = [];
req.session.history.push(newMessage);
}
res.status(200).send(allMessages);
}
};
In this step, we will display the user's message history in history modal.
- In the
HistoryModal.js
file, importaxios
and find thecomponentDidMount
method.- Make a GET request to fetch the message history for the user.
- path:
'/api/messages/history'
- Update the
historyMessages
property with the response.
- path:
- Make a GET request to fetch the message history for the user.
- Since we don't have an endpoint for the above request, let's go create one.
- In the
index.js
file, add a GET endpoint with a path of'/api/messages/history'
. - Add a method named
history
to the messages controller.history
should return all the messages stored on the session.
- In the
HistoryModal.js
import React, { Component } from 'react';
import axios from 'axios';
import './HistoryModal.css';
export default class HistoryModal extends Component {
constructor(props) {
super(props);
this.state = {
historyMessages: []
};
}
componentDidMount() {
axios.get("/api/messages/history").then(res => {
this.setState({
historyMessages: res.data
});
});
}
...
index.js
...
app.get("/api/messages", messagesCtrl.getAllMessages);
app.get("/api/messages/history", messagesCtrl.history);
app.post("/api/message", messagesCtrl.createMessage);
app.listen(SERVER_PORT, () => {
console.log(`Server listening on port ${SERVER_PORT}.`);
});
messagesCtrl.js
...
history: (req, res) => {
res.status(200).send(req.session.history);
}
};
In this step, we will add in custom middleware. Sometimes you just cannot trust some of the potty mouth people out there in the world. We are going to create middleware that will filter out bad words from our users' messages.
-
In
index.js
, add in some top level, custom middleware. If there is amessage
property on the body, write some filter logic to make sure that bad words are removed. Below is an example, but there are different ways to accomplish this.- Note: the example below is using a regular expression. Regular expressions are patterns used to match character combinations in strings. The regular expression below is searching for our bad words using the 'g' flag, which searches the string gloabally for all instances of our bad words...then replaces them with '****'.
app.use((req, res, next) => {
let badWords = ['knucklehead', 'jerk', 'internet explorer'];
if (req.body.message) {
for (let i = 0; i < badWords.length; i++) {
let regex = new RegExp(badWords[i], 'g');
req.body.message = req.body.message.replace(regex, '****');
}
next();
} else {
next();
}
});
If you see a problem or a typo, please fork, make the necessary changes, and create a pull request so we can review your changes and merge them into the master repo and branch.
© DevMountain LLC, 2019. Unauthorized use and/or duplication of this material without express and written permission from DevMountain, LLC is strictly prohibited. Excerpts and links may be used, provided that full and clear credit is given to DevMountain with appropriate and specific direction to the original content.