- Tech Stack: React, Node, Express, MongoDB
- Main Dependencies:express, dotenv, mongoose, jsonwebtoken, bcrypt, axios, styled-components
- Other dependencies: http-status-codes
- Images: https://undraw.co/illustrations
- Design: Figma https://www.figma.com/file/aOmg1JxetWpgWhHFeOFPpK/Incidentally?node-id=0%3A1
Read that ES module was created to standardize the JavaScript module system. It is also more in sync with the front end codes that uses import/export syntax instead of require(). There are 3 ways to 'activate' es module for nodejs, i prefer to the following:
// add this to server-side package.json
"type": "module"
To allow me to start the express server and react frontend concurrently in 1 terminal instead of opening 2 terminals
// add this in package.json
"start": "concurrently --kill-others-on-fail \"npm run server\" \" npm run client\"",
A object modelling tool for MongoDB. Using mongoose, I can define the schema for the MongoDB Document. Has built-in type casting, validation, query etc out of the box. It also has the pre middleware functions that can be called before we call the mogoose.model(). Allows me to hash the user password beforehand.
//Reference: https://mongoosejs.com/docs/middleware.html#pre
// this func will be called before doc is saved to db
userSchema.pre("save", async function () {
if (!this.isModified("password")) return;
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
// next();
});
Reduce the need to have alot of classnames as it automatically generate unique classname for our CSS styles. Can also pass in props to styled-components.
https://www.figma.com/file/aOmg1JxetWpgWhHFeOFPpK/Incidentally?node-id=0%3A1
// create context in appContext.js for global states to be used in any components
const AppContext = React.createContext();
// define initialState
const initialState = {showAlert: false, user: user ? JSON.parse(user) : null,}
const AppProvider = ({ children }) => {
// define children functions
return (
<AppContext.Provider value={{ ...state }}>{children}</AppContext.Provider>
);
}
//define & export a useAppContext function to be used in any components
const useAppContext = () => {
return useContext(AppContext);
};
export { AppProvider, initialState, useAppContext };
// accessing children component via useAppContext()
const { user, showAlert } = useAppContext();
import { StatusCodes } from "http-status-codes";
StatusCodes.BAD_REQUEST //400
StatusCodes.UNAUTHORIZED //401
StatusCodes.NOT_FOUND //404
StatusCodes.INTERNAL_SERVER_ERROR //500
const addUserToLocalStorage = ({ user, token }) => {
localStorage.setItem("user", JSON.stringify(user));
localStorage.setItem("token", token);
};
We can think of axios interceptors like a middleware: https://axios-http.com/docs/interceptors
// typically written like this
const updateUser = async(currentUser) =>{
try{
const {data} = await axios.patch('api/auth/updateUser', currentUser, {
// avoid repeating this header auth portion in every function
headers:{
Authorization: `Bearer ${state.token}`
}
})
} catch(error){
console.log(error.response)
}
}
// Using axios interceptors
// Add a request interceptor
const authFetch = axios.create({
baseURL: "/api",
});
authFetch.interceptors.request.use(
(config) => {
config.headers.common["Authorization"] = `Bearer ${state.token}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Add a response interceptor
// allow us to isolate and check for 401 errors
authFetch.interceptors.response.use(
(response) => {
return response;
},
(error) => {
// console.log(error.response)
if (error.response.status === 401) {
logoutUser(); //force logout if 401
}
return Promise.reject(error);
}
);
//rewrite using authFetch
const updateUser = async (currentUser) => {
dispatch({ type: UPDATE_USER_BEGIN });
try {
const { data } = await authFetch.patch("/users/update", currentUser);
const { user, token } = data;
dispatch({
type: UPDATE_USER_SUCCESS,
payload: { user, token },
});
addUserToLocalStorage({ user, token });
} catch (error) {
if (error.response.status !== 401) {
dispatch({
type: UPDATE_USER_ERROR,
payload: { msg: error.response.data.msg },
});
}
}
clearAlert();
};
docker-compose build
docker-compose up
http://localhost:3000
http://localhost:5000
For Docker, to change proxy in client's package.json to this:
"proxy": "http://<container_name>:5000",
"proxy": "http://express-server-incidentally:5000",
- Design a simple Wireframe
- Login Page
- Dashboard Page
- Consists of User and Admin user
- Admin - Create, Read, Update, Delete
- Raise incident as Admin
- Set incident type & priority
- Once raised, incident is open
- Assign incident to a User
- Receive user acknowledgement of raised incident
- Raise incident as Admin
- User - Read, Update
- Cannot raise incident (only Admin)
- Acknowledge incident assgined by Admin
- Resolve incident
- Once resolved, incident is closed
- Cannot Delete incident
- Dashboard Design
- Split-pane Table
- Indexing of incidents
- Filter/Sort by Date created/updated
- Filter/Sort by Incident Type
- Filter/Sort by Priority
- Filter/Sort by Open or Close Cases
- Pagination (react-split-pane)
- Search function
- Nice alerts messages (react-toastify)
Does try/catch and next(err) for you
const userRegister = async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json({ user });
} catch (error) {
next(error);
}
};