A user will be presented with a homepage where they can view the photo grid.
This application will be using the MERN stack. MERN stands for MongoDB, Express, React, Node, after the four key technologies that make up the stack.
- MongoDB - document database
- Express(.js) - Node.js web framework
- React(.js) - a client-side JavaScript framework
- Node(.js) - the premier JavaScript web server
Useful Links:
- Youtube Tutorial - Traversy Media
- Node Full Course - Dave Gray
- React Register & Login Course - Dave Gray
General project structuring. Split into Frontend and Backend. Each folder contains everything it needs in case it needs to be decoupled in the future.
Useful Links:
- Node Architecture - Software On The Road
- Express Architecture - LogRocket
- React Architecture - Tania Rascia
Typescript will be used across Frontend and Backend. The behaviour can be customised in tsconfig.json
. Organising and storing types, interfaces and enums will be done with a Typed-based approach. I like keeping all the types stored in a central global folder. Otherwise, I forget where I have previously created types which leads to duplicate types, interfaces and enums 😒
Useful Links:
- Typescript Cheat Sheet - Docs
- Typescript Compiler Options - Docs
- Typescript Organising Types - Become Better Programmer
Airbnb introduces it as the “Most reasonable approach to JavaScript”. To check syntax, find problems, and enforce code style. More coding rules equals a more consistent coding experience is my take on it.
Useful Links:
- Setting Up - Dev.to
- Eslint Rules Rules - Docs
- Airbnb Style Guide - Docs
- Youtube Tutorial - Traversy Media
Makes it a lot easier to import from anywhere within the project and move files around without changing imports and you never end up with something like ../../../../../components/
. Too many ../../../../../
can be extremely sore on the eyes. This was more of a pain then I thought it would be. Getting Typescript, Eslint, Jest and Vscode Intellisense to play nice was a b*tch.
See tsconfig.paths.json
in frontend and backend folders for configured paths. See jest.config.cjs
in backend folder for connection to tsconfig.paths.json
.
Useful Links:
- Config tsconfig.json Paths - Docs
- Config eslintrc.js - Alex Gorbatchev
- Stack Overflow Troubleshoot - Stack Overflow
The goal is to have good enough test coverage that any change I make does not cause a major incident.
Useful Links:
Setting up the Backend to be Typescript. Everything Typescript!
Useful Links:
MongoDB likes to think it has no rule book. So here is some rules:
- Embed unless there is a compelling reason not to
- Avoid JOINS if they can be avoided
- Array should never grow without bound
- An object should not be embedded if it needs to be accessed individually
Based on these rules. I will:
- Embed Equipment as an array for Users
- Separate Tags from Photos because I will need to access Tags separately
Users to Equipment - Embed Equipment in Users
An example of embedded data that Users and Equipment will have:
Photos to Tags - Many to Many
An example of the Many to Many relationship that Photos and Tags will have:
Testing with MongoDB & Mongoose
Decided to use an in-memory database over mocks. This will allow the tests to be directly executed on the in-memory database. This allows the tests to closely emulate production instead of using mocks that can be incorrect, incomplete or outdated 🤓
Useful Links:
- Youtube Tutorial Best Practices - MongoDB
- Youtube Tutorial Anti Patterns - MongoDB
- Mongoose Typescript Schema - The Code Barbarian
- Mongoose - Docs
- Testing with Mongoose - Jest - Medium
- Testing with Mongoose - mongodb-memory-server - Dev.to
- Mongodb-memory-server - Docs
- Unit Testing Controllers - Medium
- Regex Generator
Depending on the role of the user, they will have different authorisations.
ADMIN | USER | VIEW | |
---|---|---|---|
addUser | X | - | - |
deleteUser | X | - | - |
getUser | X | X | - |
getUsers | X | X | - |
loginUser | X | X | X |
updateUser | X | - | - |
addPhoto | X | X | - |
deletePhoto | X | X | - |
getPhoto | X | X | X |
getPhotos | X | X | X |
updatePhoto | X | X | - |
addTag | X | X | - |
deleteTag | X | X | - |
getTag | X | X | X |
getTags | X | X | X |
updateTag | X | X | - |
Useful Links:
- User Role Based Authorisation - Jason Watmore
- Node Full Course Chapter 10 Authentication - Dave Gray
The first time around in rellygudfutos_v1 Frontend handled sorting and filtering. Pagination wasn't even a thing... As you can guess, this was not a long-term solution. Loading up everything upfront did have some pros and cons.
Pros:
- Sorting was very quick (no API request needed)
- Filtering was very quick (no API request needed)
Cons:
- Long loading times for the
GET /photos
endpoint - App wont scale well as more photos are added
Sorting and Filtering will also have to be done on the Backend because there will be no front-loading of all the photos. React Query will make the fetching of the sorted/filtered/paginated data more smooth for the user.
Useful Links:
- Node Sorting, Filtering and Pagination - Medium
- MongoDB Sorting, Filtering and Pagination - Jeff Devs Life
- MongoDB Filtering Query 1 - Docs
- MongoDB Filtering Query 2 - Docs
- Youtube Pagination Tutorial - Web Dev Simplified
The photos will be uploaded to an image CDN, in this case AWS S3 Bucket. Sharp will be used to resize the image so nothing exceeds 1080 pixels in length or height
Pros for CDN:
- Performance - Better load time
- Optimize - Adjust compression quality, etc
- Scalable - Cheaper to host images on S3 than server
Useful Links:
We all love React but there is a lot of mistakes that can be made. Top things I want to avoid when creating rellygudfutos:
- Unnecessary
useStates
- If a
useState
value changes it causes a rerender. Most of the time the value can just be stored in aconst
,useRef
oruseMemo
if it needs to be memorized.
- Unnecessary
useEffects
- A lot of
useEffects
can be ugly and confusing. Group them up and create custom hooks and avoid using them in the first place if you don't have to, each usage causes a rerender.
- Unnecessary
useCallback
&useMemo
- It is tempting to memorize all pieces of data and functions but this can come at a performance cost... If the function or piece of data is a simple inline function, then setting up
useCallback
oruseMemo
can actually harm performance more than you would gain. Reserve these hooks for more complex computing that will have an obvious benefit.
- Use State Directly After
setState
-
It will contain the previous render value and not the latest
setState
value. That is why we should use the function version ofuseState
if manipulating the state and using it after.- Before:
setCount(amount);
- After:
setCount(currentCount => { return currentCount + amount });
- Referential Equality Mistakes
- An object as a hook side effect will have funny results:
- If passed from global state (Zustand, Redux) it will not trigger a rerender despite having different values because it is the same object.
- The other side is if you copy the exact same object in a component it will incorrectly trigger a hook because it is a brand new object every render. Basically
const {} === {}
will always be false. They are the same value but different objects.
- Not Aborting Fetch Requests
- Filters and searches can cause a lot of API requests. If the previous request is not finished, it should be aborted for the latest. Will help performance and jumpy UI.
- Not Using React Strict Mode
-
Will only be active for development and is useful to spot unintended side effects:
- It does everything twice. It double renders and runs functions, hooks, dispatches, etc twice
- On first render it mounts, unmounts and mounts the component again. This helps spot unintended side effects in
useEffects
where we are not using a cleanup.
Useful Links:
- Youtube React UseEffect Best Practices - Web Dev Simplified
- Youtube React Other Hook Best Practices - Web Dev Simplified
- Youtube React Strict Mode - Web Dev Simplified
- Understanding Referential Equality in React - Medium
- React Query Fetch Cancellation - Tanstack
- React React useEffect Cleanup - LogRocket
- When to useMemo and useCallback - Kent C. Dodds
I wanted to avoid slow webpack so Vite.js it is.
Useful Links:
UI library with all the components I will need. Each component used will be put into src/components
folder and will be very opinionated. The aim is to have any custom design within the component with any shared style logic. This will ensure that there is a consistent UI throughout the app.
Each component will have a className
to make it easier to customize from a parent component eg. className="rgf-list"
. rgf
prefix stands for the app name rellygudfutos.
Useful Links:
React Query as a server-side state that reduces API requests 🤤. It handles a lot of fantastic things out of the box. Refocus fetch, stale data refetch, success | loading | error states and glorious infinite queries for beautiful pagination without clicking buttons. It can fetch the data one page ahead so the experience feels seamless to the user.
Examples of hooks that will take data from API calls:
- usePhotos
- useTags
- useUsers
Useful Links:
- React Query - Docs
- React Query Infinite Query - TanStack
- React Query Infinite Query - Medium
- Youtube React Query Tutorial - Codevolution
Super lightweight client-side state. For this application, Redux is overkill when using React Query and it is easier to set up then React's useContext
.
Zustand allows for multiple stores where you can easily configure where you want the state to persist to local storage. This is really handy if you want models/dialogs or menu drawers to remain open on refresh or revisit.
Examples of data that needs to go into global state:
- Theme
- Toggling light and dark mode throughout the app.
- Config
- App wide Modals/dialogs or menu drawers being open or not.
- Gallery
- Gallery filters, search and sorting. Avoid prop drilling and just connect to the global state.
- Authorisation
- User authorisation will not be persisted to local storage for obvious security concerns. But will be very useful in a global state to check permissions for certain actions.
Useful Links:
We will not store JWT tokens in local storage or a cookie. That's not secure! A hacker could retrieve an access token from there. It is recommended for frontend client applications to only store access tokens in memory, so they will be automatically lost when the app is closed.
The API will issue refresh tokens as HttpOnly cookie, which is not accessible via JavaScript. The refresh token will have a expiration date and when expired the app will require the user to login again.
The refresh token can be terminated if the user logs out.
Useful Links:
React-hook-form is a super light weight library that is the right amount of magic without it turning into a black box of wtf (Formik is an example of that in my humble opinion). It can integrate well with Material-Ui and handle a lot of validation very smoothly.
Useful Links:
- React Hook Form - Docs
- React Hook Form Validation - Docs
- React Hook Form With Material UI - LogRocket
- Formik - Docs
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI. Better than the app crashing.
The approach will be to wrap the top-level route components to display a “Something went wrong” message to the user.
Useful Links:
Each loading state will be determined by the length of time it takes to do an API request. In some instances I will artificially increase the loading time to create a more tactile app. For example, when the user clicks into a photo there will be a minimum loading time to display a loading skelton.
Implementing a minimum loading state in an application can enhance user experience by providing a more consistent and predictable interaction. It’s best used for elements where quick loading states can cause visual disruption or where consistent loading indicators improve user perception.
The main pro is that it will reduce jitter where there is rapid state changes. A minimum loading state can smooth out these transitions, creating a more polished experience.
Next.js is a server-side rendering that reduces first contentful paint and is superior for SEO purposes. It is outputted in html vs javascript which is easier for search engines to look through 🤤
The regret is not trying Next.js out first before finishing a fully fleshed out Express Backend. I could setup a custom server with Express within Next.js or mess around with some Next.js build where it is in the Express app. But in my opinion that is not doing Next.js justice. Something like GraphQL integrated with Next.js could be pretty powerful.
Useful Links:
- Next.js - Docs
- GraphQL - Docs
- Next.js Custom Server - Docs
- Setting Up with React Query - Prateek Surana
- Youtube Next.js Tutorial - Traversy Media
- Youtube Next.js Tutorial - Fireship
I am still on the fence on this one because I like it as a pattern but I know it is perhaps bad practice.
By handling imports like import * as alias
, it sacrifices on optimal tree shaking getting rid of functions/modules that are not needed. But I find very useful for maintenance and general naming conventions. Knowing what file a function is coming from reads well in my opinion but it is bad practice to import everything and not just what you need. It is ok for small apps but could become a problem at enterprise level.
100% do not take this approach for large external libraries like lodash
or MaterialUI
, import what you need from them.
I went with the import * as alias
approach for the Backend Development and went the opposite for the Frontend Development. Knowing what I know now I would probably have done the Backend in a similar manner.
Useful Links:
Some useful commands I regularly use and what they do because I will forget.
- Kill localhost bash -
npx kill-port 5000
List of commands Yarn Cli
- Installs all the dependencies defined in a
package.json
file -yarn install
- Add package to dependencies -
yarn add package
- Add package to dev dependencies -
yarn add -D package
- Remove package from
package.json
-yarn remove package
- Check vulnerabilities -
yarn audit
- Upgrade package -
yarn upgrade package
- Status of staged and unstaged changes -
git status
- Number files and line changes with master -
git diff --stat master
- Add all changed files to staging area -
git add .
- Commit staging area files with commit message -
git commit -m 'commit message'
- Push changes to Github repo -
git push
- Pull changes from Github repo -
git pull
- Merge master into current branch -
git merge master
Using Nvm for Windows. Check Node for Windows for recommended version.
- List of Node versions installed -
nvm list
- Current Node version running -
nvm current
- Use Node version specified -
nvm use 16.15.1
- Install Node version specified -
nvm install 16.15.1
- Node version -
node -v
- Check version -
tsc --version
- Compile file -
tsc index.ts
Code snippets I use that I forget about:
- Simple React Snippets - Check extension description for snippets
- ES7+ React/Redux/React-Native Snippets
rfce
import React from 'react'
function App() {
return (
<div>App</div>
)
}
export default App
uef
useEffect(() => {
}, []);
usf
const [, set] = useState();