You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Next add error and exception handling to controllers for the first route
const postPlan = (req, res) => {
if(!req.body.name) {
res.status(400)
// Express error handling
throw new Error('Please provide a name for your plan')
}
res.status(200).json({ plan: 'posted a new plan' });;
};
Use Expresses Built-in Error Handling
To change Expresses default error handler a middleware function is needed
Create a middleware folder inside backend
Create a errorMiddleware.js file inside the middleware folder
Create a function that overrides the default error handler
const { errorHandler } = require('./middleware/errorMiddleware');
// Under the routes
app.use(errorHandler);
Make Controllers Asynchronous
Install express-async-handler
When using Mongoose in controller functions to interact with the database you get back a promise
To use the custom error handler instead of using trycatch with asynchronous controller functions install express-async-handler with npm i express-async-handler
Converting Controller functions to Asynchronous Functions
Now make all controller functions asynchronous and wrap them with the imported express-async-handler
const postPlan = asyncHandler(async (req, res) => {
if (!req.body.name) {
res.status(400);
throw new Error('Please provide a name for your plan');
}
res.status(200).json({ plan: 'posted a new plan' });
});
Import the new model and use it in its controller file
const Plan = require('../models/planModel');
const postPlan = asyncHandler(async (req, res) => {
if (!req.body.name) {
res.status(400);
throw new Error('Please provide a name for your plan');
}
const plan = await Plan.create({
name: req.body.name,
});
res.status(200).json(plan);
});
Create the Second User Model
Create a usersModel.js file inside the models folder
Add the token to login and register controller functions retruns
token: generateToken(user._id),
Protecting Routes With Authentication Middleware
Create a new authMiddleware.js file in the middleware folder
Import jwt, asyncHandler and User in authMiddleware.js
Creating the Protect Function
Get the token from the request header
Verify the token with jwt
Get the user from the token
const protect = asyncHandler(async (req, res, next) => {
let token;
// Check if the http header of the request contains an authorization object that starts with
// "Bearer" the token is send in the authorization header like this "Bearer" "token"
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
try {
// Get the token from the http authorization header
token = req.headers.authorization.split(' ')[1];
// Verify the token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Re-assingn req.user with the id from the token excluding the password
// Now req.user is available in other controller functions that user the same token
req.user = await User.findById(decoded.id).select('-password');
// Call the next piece of middleware
next();
} catch (error) {
console.log(error);
res.status(401);
throw new Error('Not authorized');
}
}
if (!token) {
res.status(401);
throw new Error('Not authorized not token');
}
});
Adding the Protect Function to Private Routes
User Routes
Import the route protector function in userRoutes
Protect the private routes by adding the protector function as the first argument
Add user field to new posted plans to ensure there is a relationship between plan and user
const postPlan = asyncHandler(async (req, res) => {
if (!req.body.name) {
res.status(400);
throw new Error('Please provide a name for your plan');
}
const plan = await Plan.create({
user: req.user.id,
name: req.body.name,
});
res.status(200).json(plan);
});
Update & delete only own plans
Import the User model
Check if the given user exists
Check if the user id matches the resource id
// Get user from database
const user = await User.findById(req.user.id);
// Check if user exists
if (!user) {
res.status(401);
throw new Error('User not found');
}
// Make sure logged in user matches the plans user
if (plan.user.toString() !== user.id) {
res.status(401);
throw new Error('User not authorized');
}
Getting Started with the Frontend
Initialize Project
Initialize the react frontend with the redux template with npx create-react-app frontend --template redux
Add a script to run the frontend in the package.json file in the root directory of the project "client": "npm start --prefix frontend"
Delete all the unwanted files from the redux template in frontend
Clear up the default App.js file as well as the store.js file in the app directory
Install Dependencies
Install react-router-dom with npm i react-router-dom
Install react-icons with npm i react-icons
Creating the First Pages
In frontend create a pages folder to store the pages of the app
Create some .jsx pages in the pages directory
Import the pages in App.js and setup basic routing for the them
Creating the Nav Component
In frontend create a components folder to store the components of the app
Create a nav component in the components directory
In the nav component, create a navbar and add some links to the routes of the app with the Link tag
Import the nav component in App.js
Creating the Register and Login Forms
Create a simple form with fields for username, email, password and confirm password
The register form can be largely re-used when creating the login page so copy and paste it there
Modify the login forms code so its ready to take in the users login information
Concurrently setup
To run the client and the server concurrently with the same script, the concurrently package is needed
Install concurrently with npm i concurrently
Create the script for running the client and the server concurrently in the package.json found in the root directory
"dev": "concurrently \"npm run server\" \"npm run client\" "
Redux
Authentication State setup
To handle the user authentication state a slice file is needed
Create a features directory in frontend
Create a auth directory in features
Create a authSlice.js file in the auth directory
First Slice File
Import createSlice and createAsyncThunk from redux-toolkit in authSlice.js
Get the user token from localStorage
Create the initial state object for the authentication state
const user = JSON.parse(localStorage.getItem('user'));
const initialState = {
user: user ? user : null,
isError: false,
isSuccess: false,
isLoading: false,
message: '',
};
Reducers help manage the state and keep it up to date
Add a pending, fulfilled and rejected extra reducer cases for the login function
Dispatching the Login Function
The login function is used in the login page
Import the reset and login functions from authSlice
Import useSelector, useDispatch and useNavigate
Initialize dispatch and navigate
Select the states needed from authSlice
Building the Submit Logic
Handle form validation in the onSubmit function
Dispatch the login function with the users data in the onSubmit function of the login page
React to Redux State with useEffect
Create a useEffect and add the redux state dependencies used in the login page eg. isErroruser
Check for errors and add a prompt (toast) for the user with the message from redux state
Check for successful login of user and handle consequent navigations
Lasty dispatch the reset function at the end of useEffect to reset any potentially irrelevant redux state
Restrict Logged Out Users From the Homepage
In this scenario users are not allowed on this page (homepage), and they should be redirected to the login page
Import useSelector, useEffect and useNavigate
Initialize navigate
Select the states needed from authSlice
Create a useEffect that checks if the user is logged in and redirect logged out users in it
Deployment
Pointing the Server to the Static Files of the App
// Serve client
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '../frontend/build')));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, '../', 'frontend', 'build', 'index.html'));
});
} else {
app.get('/', (req, res) => {
res.send('The application in not in production');
});
}
Deploying to Heroku
Install the Heroku CLI
Run heroku Login
Create a Heroku app with heroku create <app_name>
Add the projects environmental variables in Heroku eg. NODE_ENV
Write a Heroku post build script in the package.json file of the root directory: "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install --prefix frontend && npm run build --prefix frontend"