Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/roger-takeshita/mern_boilerplate
MERN Stack WebApp: MongoDB, Express.js, React.js/Redux.js, Node.js, Jestjs, JSON Web Token (JWT), REST API, JavaScript, HTML, and CSS/SASS (BEM)
https://github.com/roger-takeshita/mern_boilerplate
bem-methodology express jwt mern-stack mongodb mongoose nodejs react redux rest-api sass
Last synced: about 1 month ago
JSON representation
MERN Stack WebApp: MongoDB, Express.js, React.js/Redux.js, Node.js, Jestjs, JSON Web Token (JWT), REST API, JavaScript, HTML, and CSS/SASS (BEM)
- Host: GitHub
- URL: https://github.com/roger-takeshita/mern_boilerplate
- Owner: Roger-Takeshita
- Created: 2020-05-03T21:07:04.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2023-01-24T02:26:48.000Z (almost 2 years ago)
- Last Synced: 2023-03-07T05:36:35.145Z (almost 2 years ago)
- Topics: bem-methodology, express, jwt, mern-stack, mongodb, mongoose, nodejs, react, redux, rest-api, sass
- Language: JavaScript
- Homepage:
- Size: 1.75 MB
- Stars: 3
- Watchers: 2
- Forks: 1
- Open Issues: 20
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# My Full-stack Base
### Last time updated: 05/08/2020
Summary
* [Front-end](#frontend)
* [Installation](#frontendinstall)
* [Assets Folder](#assets)
* [Components Folder](#components)
* [ErrorMessage Component](#errormessage)
* [Footer Component](#footer)
* [Form Log In Component](#formlogin)
* [Form Profile Component](#formprofile)
* [Form Sign Up Component](#formsignup)
* [Header Component](#header)
* [Navbar Component](#navbar)
* [Modal Message Component](#modalmessage)
* [Style Css](#css)
* [Pages](#pages)
* [Redux](#redux)
* [Utilities Folder](#utils)
* [apiService](#apiservice)
* [tokenService](#tokenservice)
* [userService](#userservice)
* [App.js](#app)
* [Index.js](#index)
* [Store.js](#store)
* [Back-end](#backend)
* [Installation](#backendinstall)
* [package.json](#packagebackend)
* [Environment - Files](#backendenv)
* [Database - Connection](#databaseconnection)
* [Controllers - User](#controllers)
* [Middelwares - Auth](#middlewares)
* [User Model - Schema](#model)
* [User Routes](#routes)
* [Server Configuration](#server)
* [Server - app.js](#app)
* [Server - index.js](#index)
* [User's API Test Cases - Jest](#tests)
Front-end
Installation
[Go Back to Summary](#summary)
* First create a new react app
```Bash
npx create-react-app my-full-stack-app
```* The react boiler plate will generate the following structure
```Bash
.
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png <- Delete
│ ├── logo512.png <- Delete
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.css <- Delete
│ ├── App.js
│ ├── App.test.js <- Delete
│ ├── index.css <- Delete
│ ├── index.js
│ ├── logo.svg <- Delete
│ ├── serviceWorker.js
│ └── setupTests.js <- Delete
├── .gitignore
├── package-lock.json
├── package.json
└── README.md
```* Create the following folders and files
* Move all the react boiler plate into a new folder called `Front-end`, and then create the following structure.```Bash
mkdir Front-end/assets Front-end/icons Front-end/images Front-end/components Front-end/components/ErrorMessage Front-end/components/Footer Front-end/components/FormLogin Front-end/components/FormProfile Front-end/components/FormSignup Front-end/components/Header Front-end/components/ModalMessage Front-end/components/Navbar Front-end/css Front-end/pages Front-end/pages/AboutPage Front-end/pages/HomePage Front-end/pages/LoginPage Front-end/pages/ProfilePage Front-end/pages/SingupPage Front-end/redux Front-end/utilstouch Front-end/assets/iconsSocialMedia.js Front-end/assets/iconsSvg.js Front-end/components/ErrorMessage/ErrorMessage.jsx Front-end/components/Footer/Footer.jsx Front-end/components/FormLogin/FormLogin.jsx Front-end/components/FormProfile/FormProfile.jsx Front-end/components/FormSignup/FormSignup.jsx Front-end/components/Header/Header.jsx Front-end/components/ModalMessage/ModalMessage.jsx Front-end/components/Navbar/Navbar.jsx Front-end/css/index.css Front-end/css/index.scss Front-end/pages/AboutPage/AboutPage.js Front-end/pages/HomePage/HomePage.js Front-end/pages/LoginPage/LoginPage.js Front-end/pages/ProfilePage/ProfilePage.js Front-end/pages/SingupPage/SingupPage.js Front-end/redux/user.js Front-end/utils/apiService.js Front-end/utils/tokenService.js Front-end/utils/userService.js Front-end/store.js
```* Final front-end structure
```Bash
.
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── assets
│ │ ├── icons
│ │ │ ├── github_footer.svg
│ │ │ ├── github.svg
│ │ │ ├── jest.svg
│ │ │ ├── linkedin.svg
│ │ │ ├── redux.svg
│ │ │ └── replit.svg
│ │ ├── images
│ │ │ └── RogerTakeshita.jpeg
│ │ ├── iconsSocialMedia.js
│ │ └── iconsSvg.js
│ ├── components
│ │ ├── ErrorMessage
│ │ │ └── ErrorMessage.jsx
│ │ ├── Footer
│ │ │ └── Footer.jsx
│ │ ├── FormLogin
│ │ │ └── FormLogin.jsx
│ │ ├── FormProfile
│ │ │ └── FormProfile.jsx
│ │ ├── FormSignup
│ │ │ └── FormSignup.jsx
│ │ ├── Header
│ │ │ └── Header.jsx
│ │ ├── ModalMessage
│ │ │ └── ModalMessage.jsx
│ │ └── Navbar
│ │ └── Navbar.jsx
│ ├── css
│ │ ├── index.css
│ │ └── index.scss
│ ├── pages
│ │ ├── AboutPage
│ │ │ └── AboutPage.jsx
│ │ ├── HomePage
│ │ │ └── HomePage.js
│ │ ├── LoginPage
│ │ │ └── LoginPage.js
│ │ ├── ProfilePage
│ │ │ └── ProfilePage.js
│ │ └── SignupPage
│ │ └── SignupPage.js
│ ├── redux
│ │ └── user.js
│ ├── utils
│ │ ├── apiService.js
│ │ ├── tokenService.js
│ │ └── userService.js
│ ├── App.js
│ ├── index.js
│ ├── serviceWorker.js
│ └── store.js
├── package-lock.json
└── package.json
```Assets Folder
[Go Back to Summary](#summary)
* In `assets/icons`
* Add the SVG icons* In `assets/images`
* Add your images and logos* In `assets/iconsSocialMedia.js`
* In `assets/iconsSvg.js`
* This is a helper file to easily order and display your social media links e icons
* Inside the about page we have an `.map()` method to loop though this document and mount all the images/icons.Components Folder
ErrorMessage Component
[Go Back to Summary](#summary)
* We create a modular component to only display the error message from the server
* We basically need to send two properties to this component, `message` (string) and `doneErrorMessage` (method).
* the `doneErrorMessage` sends back to the parent component to the `ErrorMessage` component after `5 secs````JavaScript
import React, { useEffect } from 'react';function ErrorMessage({ message, doneErrorMessage }) {
useEffect(() => {
const timer = setTimeout(() => {
doneErrorMessage();
}, 5000);
return () => {
clearTimeout(timer);
};
}, [message, doneErrorMessage]);return
{message};
}export default ErrorMessage;
```Footer Component
[Go Back to Summary](#summary)
* Basic footer component
```JavaScript
import React from 'react';
import githubLogo from '../../assets/icons/github_footer.svg';function Footer() {
return (
);
}export default Footer;
```Form Log In Component
[Go Back to Summary](#summary)
* Inside this component we have a `form` variable that holds the form object with `email`, `password` and `message`
* After a successful log in:
* We receive a token from the server, and then we execute a callback to store this token in the `localStorage`.
* Then we get the user information from the `localStorage` to set a user to our redux with `loginUser()` action.Form Profile Component
[Go Back to Summary](#summary)
* Similar to `FormLogin` component
* Inside this component we have a `form` variable that hold the form object with `firstName`, `lastName`, `email`, `password`, `newPassword`, `confirmNewPassword`, and `message`;
* After a successful update:
* We receive an `updateed` token from the server, and then we execute a callback to `update` the new token in the `localStorage`.
* Then we get the `updated` user information from the `localStorage` to `update` our redux with `updateUser()` action.
* IMPORTANT: Before updating the user information, we first check if the password is correct.Form Sign Up Component
[Go Back to Summary](#summary)
* Similar to `FormLogin` component
* Inside this component we have a `form` variable that hold the form object with `firstName`, `lastName`, `email`, `password`, `confirmPassword`
* After a successful log in:
* We receive a token from the server, and then we execute a callback to store this token in the `localStorage`.
* Then we get the user information from the `localStorage` to set a user to our redux with `signupUser()` action.Header Component
[Go Back to Summary](#summary)
* Basic `` component (parent component)
* We import the `Navbar` component (child component)```JavaScript
import React from 'react';
import Navbar from '../Navbar/Navbar';function Head({ history }) {
return (
);
}export default Head;
```Navbar Component
[Go Back to Summary](#summary)
* This is a child component from the `Header` component
* In this file we have a `logoutUserFromApp` to clean the redux store once the user logs out.
* The logout is very simple, we don't user anything on the server side. It basically deletes the `token` from `localStore`.
* We also have a `Modal` component to double check if user wants to delete his account.Modal Message Component
[Go Back to Summary](#summary)
* A modular component to display messages to the user.
* This component receives:
* `title`
* `messsage`, html message
* `cancelLabel`, custom cancel label
* `okLabel`, custom ok/submit/confirm label
* `handleCancelModal` method, to listen the cancel button
* `handelOkModal` method, to listen the ok/confirm/submit button
* `show modal` method, to display the componentStyle Css
[Go Back to Summary](#summary)
* We are using `SASS` compiler to create our `index.css`
* We follow the `BEM` structure ([BEM Link](http://getbem.com/introduction/))Pages
[Go Back to Summary](#summary)
* All the pages components are very simple, since we created modular components.
* The only page that is not modular is the `AboutPage` since it's only to display the developer's information, and technologies used.Redux
[Go Back to Summary](#summary)
* We have a basic user redux, to manage the user information across the app.
* There are 4 actions `LOGIN_USER`, `LOGOUT_USER`, `UPDATE_USER`, and `SIGNUP_USER````JavaScript
import userService from '../utils/userService';const LOGIN_USER = 'LOGIN_USER';
const LOGOUT_USER = 'LOGOUT_USER';
const UPDATE_USER = 'UPDATE_USER';
const SIGNUP_USER = 'SIGNUP_USER';export const loginUser = () => ({
type: LOGIN_USER,
payload: userService.getUser()
});export const logoutUser = () => {
userService.logoutUser();
return {
type: LOGOUT_USER
};
};export const signupUser = () => ({
type: SIGNUP_USER,
payload: userService.getUser()
});export const updateUser = () => ({
type: UPDATE_USER,
payload: userService.getUser()
});function userReducer(state = userService.getUser(), action) {
switch (action.type) {
case LOGIN_USER:
return action.payload;
case LOGOUT_USER:
return null;
case SIGNUP_USER:
return action.payload;
case UPDATE_USER:
return action.payload;
default:
return state;
}
}export default userReducer;
```Utilities Folder
apiService
[Go Back to Summary](#summary)
* A helper file to handle API request to the server
```JavaScript
import tokenService from './tokenService';function apiRequestHelper(type, url, auth = false, data) {
const option = {
method: type,
headers: new Headers({
'Content-Type': 'application/json',
Authorization: 'Bearer ' + tokenService.getToken()
})
};
if (data && type !== 'GET') option.body = JSON.stringify(data);
return fetch(url, option).then(async (res) => {
const response = await res.json();
if (res.ok) return response;
throw new Error(response.message);
});
}export default apiRequestHelper;
```tokenService
[Go Back to Summary](#summary)
* A helper file to handle the Token that we receives from our back-end.
```JavaScript
function setToken(token) {
if (token) {
localStorage.setItem('token', token);
} else {
localStorage.removeItem('token');
}
}function updateToken(token) {
if (token) {
localStorage.setItem('token', token);
}
}function getToken() {
let token = localStorage.getItem('token');
if (token) {
const payload = JSON.parse(atob(token.split('.')[1]));
//? atob() - decoding a base-64 encoded string. It is used to decode a string of data which has been encoded using the btoa() method.
//? JSON.parse - Converting back a json object(
if (payload.exp < Date.now() / 1000) {
localStorage.removeItem('token');
token = null;
}
}
return token;
}function getUserFromToken() {
const token = getToken();
return token ? JSON.parse(atob(token.split('.')[1])) : null;
}function removeToken() {
localStorage.removeItem('token');
}export default {
setToken,
getToken,
getUserFromToken,
removeToken,
updateToken
};
```userService
[Go Back to Summary](#summary)
* A helper file to handle client side request related to the user
```JavaScript
import apiRequestHelper from './apiService';
import tokenService from './tokenService';
const URL = '/api/users';function loginUser(data = { email: undefined, password: undefined }) {
const url = `${URL}/login`;
return apiRequestHelper('POST', url, false, data).then(({ token }) => {
tokenService.setToken(token);
});
}function getUser() {
return tokenService.getUserFromToken();
}function getUserProfile() {
const url = `${URL}/me`;
return apiRequestHelper('GET', url, true);
}function signupUser(
data = { firstName: undefined, lastName: undefined, email: undefined, password: undefined }
) {
const url = `${URL}/signup`;
return apiRequestHelper('POST', url, false, data).then(({ token }) => {
tokenService.setToken(token);
});
}function updateUser(
data = { firstName: undefined, lastName: undefined, email: undefined, password: undefined }
) {
const url = `${URL}/me`;
return apiRequestHelper('PUT', url, true, data).then(({ token }) => {
tokenService.updateToken(token);
});
}function deleteUser() {
const url = `${URL}/me`;
return apiRequestHelper('DELETE', url, true);
}function logoutUser() {
tokenService.removeToken();
}export default {
getUser,
getUserProfile,
loginUser,
signupUser,
updateUser,
deleteUser,
logoutUser
};
```App.js
[Go Back to Summary](#summary)
* Here we have all the rules to display each route
* We have private routes and public routes
* private can only be seen by logged users
* If the route doesn't exist, the user will be redirect to home page or login page.
* We use the user redux to determine which path is available to the guest```JavaScript
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { connect } from 'react-redux';import Header from './components/Header/Header';
import Footer from './components/Footer/Footer';
import HomePage from './pages/HomePage/HomePage';
import AboutPage from './pages/AboutPage/AboutPage';
import LoginPage from './pages/LoginPage/LoginPage';
import SignupPage from './pages/SignupPage/SignupPage';
import ProfilePage from './pages/ProfilePage/ProfilePage';function App({ history, firstName }) {
let pages = firstName ? (
} />
} />
} />
} />
) : (
} />
} />
} />
} />
} />
);return (
{pages}
);
}const mapStateToProps = (state) => ({
firstName: state.user ? state.user.firstName : undefined
});export default connect(mapStateToProps)(App);
```Index.js
[Go Back to Summary](#summary)
* We have to import all the tools necessary to make it work redux (`react-redux`) and react routes (`react-router-dom`)
* Here we connect our app to the store```JavaScript
import React from 'react';
import ReactDOM from 'react-dom';
import './css/index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';ReactDOM.render(
,
document.getElementById('root')
);serviceWorker.unregister();
```Store.js
[Go Back to Summary](#summary)
* We create a redux store to combine all reducers into one place.
* We can also user middlewares to help us manage the store, such as `redux-logger````JavaScript
import { createStore, combineReducers, applyMiddleware } from 'redux';
import userReducer from './redux/user';
import logger from 'redux-logger';const reducers = combineReducers({
user: userReducer
});const store = createStore(reducers, applyMiddleware(logger));
export default store;
```Back-end
Installation
[Go Back to Summary](#summary)
* Create the following folders and files
```Bash
mkdir Back-end Back-end/config Back-end/controllers Back-end/env Back-end/middlewares Back-end/models Back-end/routes Back-end/tests Back-end/tests/fixtures
touch Back-end/config/database.js Back-end/controllers/user.js Back-end/env/test.env Back-end/env/dev.env Back-end/middlewares/auth.js Back-end/models/user.js Back-end/routes/users.js Back-end/app.js Back-end/index.js Back-end/tests/fixtures/database.js Back-end/tests/user.test.js Back-end/app.js Back-end/index.js
```* Final back-end structure
```Bash
.
├── config
│ └── database.js
├── controllers
│ └── user.js
├── env
│ ├── dev.env
│ └── test.env
├── middlewares
│ └── auth.js
├── models
│ └── user.js
├── routes
│ └── users.js
├── tests
│ ├── fixtures
│ │ └── database.js
│ └── user.test.js
├── app.js
├── index.js
├── package-lock.json
└── package.json
```* Packages
```Bash
npm i express
npm i env-cmd
npm i jest --save-dev
npm i supertest --save-dev
npm i validator
npm i mongoose
npm i bcrypt
npm i jsonwebtoken
npm i morgan
```package.json
[Go Back to Summary](#summary)
* Add the test and dev environments
```JavaScript
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server-dev": "env-cmd -f ./env/dev.env nodemon index.js",
"server-test": "env-cmd -f ./env/test.env jest --watch --runInBand --detectOpenHandles"
},
"jest": {
"bail": 1,
"verbose": true,
"testEnvironment": "node"
},
```* In the end of the file add a proxy, this will handle the server port vs react port
```JavaScript
"proxy": "http://localhost:3001"
```Environment - Files
[Go Back to Summary](#summary)
* in `./env`
* Development environment `dev.env`
```Bash
MONGODB_URL=mongodb://127.0.0.1:27017/newprojectDB
JWT_SECRET_KEY=mysupersecretkey
PORT=3001
```* Test environment `test.env`
```Bash
MONGODB_URL=mongodb://127.0.0.1:27017/newprojectDB-test
JWT_SECRET_KEY=mysupersecretkey
PORT=3001
```Database - Connection
[Go Back to Summary](#summary)
* in `config/database.js`
```JavaScript
const mongoose = require('mongoose');
const db = mongoose.connection;mongoose.connect(process.env.DATABASE_URL, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
});db.once('connected', () => {
console.log(`Connected to MongoDB ${db.name} at ${db.host}:${db.port}`);
});
```Controllers - User
[Go Back to Summary](#summary)
* in `controllers/user.js`
```JavaScript
const User = require('../models/user');
const { createJWT } = require('../middlewares/auth');
const { isEmail } = require('validator');const signupUser = async (req, res) => {
try {
if (
!req.body.firstName ||
!req.body.lastName ||
!isEmail(req.body.email) ||
req.body.password.length < 7
)
return res.status(400).json({ message: 'Invalid credentials' });
const user = await User.findOne({ email: req.body.email });
if (user) return res.status(400).json({ message: 'Email already taken' });
const newUser = new User(req.body);
await newUser.save();
const token = await createJWT(newUser);
res.status(201).json({ token });
} catch (error) {
res.status(500).json({ message: 'Something went wrong', error });
}
};const deleteUser = async (req, res) => {
try {
const user = await User.findOneAndDelete({ _id: req.user._id });
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
} catch (error) {
res.status(500).json({ message: 'Something went wrong', error });
}
};const updateUser = async (req, res) => {
try {
//! the findIdAndUpdate method bypasses mongoose
//! It performs a direct operation on the database
//+ this means that our middleware won't be executed
const user = await User.findOne({ _id: req.user._id });
if (!user) return res.status(404).json({ message: 'User not found' });
user.comparePassword(req.body.password, async (err, isMatch) => {
if (isMatch) {
user.firstName = req.body.firstName;
user.lastName = req.body.lastName;
if (req.body.newPassword !== '') user.password = req.body.newPassword;
await user.save();
const token = createJWT(user);
return res.json({ token });
}
res.status(400).json({ message: 'Wrong password!' });
});
} catch (error) {
res.status(500).send({ message: 'Something went wrong', error });
}
};const loginUser = async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user) return res.status(404).json({ message: "User doesn't exist" });
user.comparePassword(req.body.password, async (error, isMatch) => {
if (!isMatch || error)
return res.status(400).json({ message: 'Unable to login, bad credentials' });
const token = await createJWT(user);
res.json({ token });
});
} catch (error) {
res.status(500).json({ message: 'Something went wrong', error });
}
};const userProfile = async (req, res) => {
try {
const user = await User.findOne({ _id: req.user._id });
if (!user) return res.status(404).json({ message: 'User not found' });
res.json(user);
} catch (error) {
res.status(500).json({ message: 'Something went wrong', error });
}
};module.exports = {
signupUser,
deleteUser,
updateUser,
loginUser,
userProfile
};
```Middelwares - Auth
[Go Back to Summary](#summary)
* in `middlewares/auth`
* Let's create a middleware to help us with the authentication. This file will:
* Check if the request token is valid before executing any private route
* Create a new token when necessary```JavaScript
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET_KEY;const authJWT = async (req, res, next) => {
try {
let token = req.get('Authorization') || req.query.token || req.body.token;
if (token) {
token = token.replace('Bearer ', '');
const user = await jwt.verify(token, SECRET);
if (!user) return res.status(400).json({ message: 'Not Authorized' });
req.user = user;
return next();
}
res.status(401).json({ message: 'Please authenticate first.' });
} catch (error) {
res.status(500).json({ message: 'Something went wrong.' });
}
};const createJWT = (user) =>
jwt.sign({ _id: user._id, firstName: user.firstName, lastName: user.lastName }, SECRET, {
expiresIn: '1 day'
});module.exports = {
authJWT,
createJWT
};
```User Model - Schema
[Go Back to Summary](#summary)
* in `models/user.js`
```JavaScript
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const validator = require('validator');
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 6;const userSchema = new Schema(
{
firstName: {
type: String,
required: true,
trim: true
},
lastName: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
trim: true,
unique: true,
lowercase: true,
validate: async (value) => {
if (!(await validator.isEmail(value))) {
throw new Error('Email is invalid');
}
}
},
password: {
type: String,
required: true,
minlength: 7,
trim: true,
validate(value) {
if (value.toLowerCase().includes('password')) {
throw new Error('Password cannot contain "password"');
}
}
}
},
{
timestamps: true
}
);//! Just before saving
userSchema.pre('save', async function (next) {
const user = this;
if (user.isModified('password')) user.password = await bcrypt.hash(user.password, SALT_ROUNDS);
next();
});//! Create a custom mongoose method
userSchema.methods.comparePassword = function (tryPassword, callback) {
bcrypt.compare(tryPassword, this.password, callback);
};//! Remove fields before sending back
userSchema.set('toJSON', {
transform: function (doc, ret) {
delete ret.password;
delete ret.createdAt;
delete ret.updatedAt;
delete ret.__v;
return ret;
}
});module.exports = mongoose.model('User', userSchema);
```User Routes
[Go Back to Summary](#summary)
* in `routes/users.js`
```JavaScript
const express = require('express');
const router = express.Router();
const userCtrl = require('../controllers/user');
const { authJWT } = require('../middlewares/auth');//! Public route
router.post('/signup', userCtrl.signupUser);
router.post('/login', userCtrl.loginUser);//! Private Route
router.get('/me', authJWT, userCtrl.userProfile);
router.delete('/me', authJWT, userCtrl.deleteUser);
router.put('/me', authJWT, userCtrl.updateUser);module.exports = router;
```Server Configuration
* We split the server configuration into two files.
* In the future this will help us to test the server's APIs with `jest`Server - app.js
[Go Back to Summary](#summary)
```JavaScript
const express = require('express');
const logger = require('morgan');require('./config/database');
const app = express();app.use(logger('dev'));
app.use(express.json());app.use('/api/users', require('./routes/users'));
app.get('/*', (req, res) => {
res.status(404).json({ message: "Path doesn't exist" });
});module.exports = app;
```Server - index.js
[Go Back to Summary](#summary)
```JavaScript
const app = require('./app');
const port = process.env.PORT || 3001;app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
```User's API Test Cases - Jest
[Go Back to Summary](#summary)
* in `tests/fixtures/database.js`
* Database access, delete users, create 4 users```JavaScript
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
const User = require('../../models/user');class NewUser {
constructor(firstName, lastName, email, password) {
this._id = mongoose.Types.ObjectId();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
this.token = jwt.sign({ _id: this._id }, process.env.JWT_SECRET_KEY);
}
}const userOne = new NewUser('Roger', 'T', '[email protected]', 'bananinha');
const userTwo = new NewUser('Thaisa', 'S', '[email protected]', 'bananinha');
const userThree = new NewUser('Yumi', 'S', '[email protected]', 'bananinha');
const userFour = new NewUser('Mike', 'T', '[email protected]', 'bananinha');const setupDatabase = async () => {
await User.deleteMany();
await new User(userOne).save();
await new User(userTwo).save();
await new User(userThree).save();
await new User(userFour).save();
};module.exports = {
userOne,
userTwo,
userThree,
userFour,
setupDatabase
};
```* in `tests/user.test.js`
* User's test cases```JavaScript
const app = require('../app');
const User = require('../models/user');
const request = require('supertest');
const jwt = require('jsonwebtoken');
const { userOne, userTwo, userThree, userFour, setupDatabase } = require('./fixtures/database');
const URL = '/api/users';beforeEach(setupDatabase);
test('Should signup new user', async () => {
const newUser = {
firstName: 'Joy',
lastName: 'A',
email: '[email protected]',
password: 'bananinha'
};
const response = await request(app).post(`${URL}/signup`).send(newUser).expect(201);
const data = await jwt.verify(response.body.token, process.env.JWT_SECRET_KEY);
const user = await User.findById(data._id);
expect(user).not.toBeNull();
expect(data).toMatchObject({
firstName: 'Joy',
lastName: 'A'
});
});test('Should login existing user', async () => {
const response = await request(app)
.post(`${URL}/login`)
.send({ email: userOne.email, password: userOne.password })
.expect(200);
const data = await jwt.verify(response.body.token, process.env.JWT_SECRET_KEY);
expect(data).toMatchObject({
firstName: userOne.firstName,
lastName: userOne.lastName
});
});test('Should not login user, bad credentials', async () => {
await request(app)
.post(`${URL}/login`)
.send({
email: userTwo.email,
password: userTwo.password + 'bad password'
})
.expect(400);
});test('Should get user profile authenticated user', async () => {
const response = await request(app)
.get(`${URL}/me`)
.set('Authorization', `Bearer ${userThree.token}`)
.expect(200);
expect(response.body).toMatchObject({
firstName: userThree.firstName,
lastName: userThree.lastName
});
});test('Should not get user profile unauthenticated user', async () => {
await request(app).get(`${URL}/me`).expect(401);
});test('Should delete authenticated user', async () => {
const response = await request(app)
.delete(`${URL}/me`)
.set('Authorization', `Bearer ${userThree.token}`)
.expect(200);
const user = await User.findById(response.body._id);
expect(user).toBeNull();
});test('Should not delete unauthenticated user', async () => {
await request(app).delete(`${URL}/me`).expect(401);
});test('Should update profile authenticated user', async () => {
const response = await request(app)
.put(`${URL}/me`)
.set('Authorization', `Bearer ${userFour.token}`)
.send({
firstName: 'Mike',
lastName: 'Cabecinha',
newPassword: '',
confirmNewPassword: '',
password: 'bananinha'
})
.expect(200);
const data = await jwt.verify(response.body.token, process.env.JWT_SECRET_KEY);
const user = await User.findById(data._id);
expect(user).not.toBeNull();
expect(user).toMatchObject({
firstName: 'Mike',
lastName: 'Cabecinha'
});
});test('Should not update profile unauthenticated user', async () => {
await request(app)
.put(`${URL}/me`)
.send({
firstName: 'Mike',
lastName: 'Cabecinha'
})
.expect(401);
});test('Should not update profile authenticated user, invalid fields', async () => {
await request(app)
.put(`${URL}/me`)
.set('Authorization', `Bearer ${userFour.token}`)
.send({
name: 'Mike',
last: 'Cabecinha'
})
.expect(400);
const user = await User.findById(userFour._id);
expect(user).not.toBeNull();
expect(user).toMatchObject({
firstName: 'Mike',
lastName: 'T'
});
});
```