- Overview
- Getting Started
- Project Brief
- Technologies Used
- Team Responsibilities
- Approach
- Featured Code
- App Walk Through
- Enhancements
- Wins, Challenges & Learnings
This was the third project from the Software Engineering Immersive Course I took at General Assembly London, built after 8 weeks of class.
It was a group project with 4 team members and the timeline was 9 days.
The application allows users to browse and search recipes by name or ingredients, filter by dietary requirements, create and share their own recipes as well as commenting others' and saving their favourites to their profile.
It is a full stack app built with React JS, Node.js, Express, Mongoose and talking to a NoSQL MongoDB database. It has been deployed via Heroku.
Click the "Code" button on the repository and choose either to Clone it or to Download ZIP the files.
You need to install Node.js to be able to proceed with the rest of the steps. Once installed and the repository files are on your machine, open 3 tabs in your Command Line Interface (CLI):
- On tab 1, run
mongod --dbpath ~/data/dbto start running MongoDB - On tab 2, navigate to the root of the project directory and run
npm ito install the backend dependencies and finallynpm run startto start running Express - On tab 3, navigate to the client directory within the project directory and run
npm ito install the frontend dependencies and finally runnpm run serveto start running the app in your browser
- Work in a team, using git to code collaboratively.
- Build a full-stack application by making your own backend and your own front-end
- Use an Express API to serve your data from a Mongo database
- Consume your API with a separate front-end built with React
- Be a complete product which most likely means multiple relationships and CRUD functionality for at least a couple of models
- Implement thoughtful user stories/wireframes that are significant enough to help you know which features are core MVP and which you can cut
- Have a visually impressive design
- Be deployed online so it's publicly accessible
- Have automated tests for at least one RESTful resource on the back-end
| Frontend | Backend | Testing | Development Tools | Public APIs |
|---|---|---|---|---|
| React JS | Express | Chai | VS Code | Edamam |
| React Router | Mongoose | Mocha | Git (branching) | |
| Babel | MongoDB | Supertest | GitHub | |
| Webpack | bcrypt | Google Chrome Dev Tools | ||
| Axios | jwt | Zoom | ||
| Lodash | Webpack | Slack | ||
| Bulma | Node.js | Google Slides | ||
| Sass | Canva | |||
| Node.js | Insomnia |
| Fabien Depasse | Kate Joyce | Jess Karia | Emily Randall | |
|---|---|---|---|---|
| Backend | Recipe reviews Generating random recipes Searching recipes |
User authentication Saving recipes Getting recipes by user |
User registration User authentication User profile editing Recipe creating/editing |
Getting all recipes Getting one recipe Recipe creating/editing |
| Frontend | homepage Search functionality All Recipes page: filtering |
User profile page Carousels |
User register/login/logout Create/Edit a new recipe Nav Bar |
Single Recipe Page Reviews |
We decided we would have two types of users for our app: guests only and registered users. We represented by a flow chart each user experience on our app. We detailed what functionalities they will be able to get, what pages they will be presented with and which backend requests our frontend would need to support our user features.
In order to get a large enough amount of recipe, we decided to fetch data from the Edamam API. It offered a large amount of data and the had the fields we needed to support our features. We fetched 500 recipes from the API, using some of most popular food as keywords.
Based on our flow chart and knowing we had the data to support our app, we moved on to designing our Models. At this stage we listed the fields we needed and their data types. We then moved on to decide which end points we would require (views) and what controllers / request type (GET, PUT, POST, DELETE) each of them will handle. Finally we listed the middlewares we would need: a secure route so we could add permission to certain end points (i.e. users can only edit/delete recipe they have created themselves), an error handler and a request logger.
We first worked as a group on the code base we all would need to develop our app features. We started by configuring our Express App including the connection to our MongoDB database, Mongoose and the directory hierarchy for our projects. We then designed our User and Recipe Models together as well as our middlewares. We built our seed file to fetch data from the Edamam API. We finally proceeded with seeding our database which also allowed us to check our models were designed as we expected. At this point we shared the code base between all of us and split the responsibilities. Each of us built part of the controllers and made sure to add user permission wherever required by using our secureRoute middleware.
Once our backend was up and running we all practiced writing integration tests using Chai, Mocha and Supertest libraries. We particularly focused on testing the user login and registration but we also tested we were getting our recipe data properly. We set up a test database which gets seeded at the start of each test and dropped at the end of each test. This was to ensure consistency in the data we are testing. Therefore if our tests were passing or failing, we knew it would be because of a genuine bug in our code as oppose to some data that has changed.
describe('Testing recipes end points', () => {
// Seed a fresh database before each test, before and afterEach are from Mocha
beforeEach(done => {
setup(done)
})
afterEach(done => {
tearDown(done)
})
// Test 1
it('Should create an array of 5 recipes and return a 200 status', done => {
api.get('/api/recipes')
.end((err, res) => {
expect(res.status).to.eq(200)
expect(res.body).to.be.an('array')
expect(res.body.length).to.eq(5)
done()
})
})
})
We used Google Slides to wireframe our User Interface as a group. We listed all the pages we would need and what each would look like depending if a user is logged in or not. We also defined how to navigate through the app and planned where the links would sit.
A few examples of our wireframe
Once we understood which pages we will have on our frontend, we broke them down into React components and allocated features to build to each member of the team. We had regular catch ups as a team to check on each other's progress and troubleshoot blockers as a group.
As we built components, we all added minimum styling using the CSS framework Bulma. Once all our features were completed we agreed on a styling theme to make sure we have consistency in the final product. Each team member has been assigned a few components to style in line with the agreed scheme. The vast majority of the styling has been done with Bulma and minimal customised CSS.
One of the most interesting challenge I have worked on during this project was to build the search feature both on the backend and on the frontend.
On the backend, I created a view handling a get request for some search data the user would input on the frontend. The search data would be received as query parameters q on the backend and using the mongoose method .find(), would find recipes matching the search data and return them with a 200 status code.
// route
router.route('/search')
.get(recipes.searchRecipe)
// controller
async function searchRecipe(req, res, next) {
const searchData = req.query.q
try {
const recipeList = await Recipes.find({ $text: { $search: `'${searchData}'` } }).populate('user').populate('comments.user')
res.status(200).send(recipeList)
} catch (err) {
console.log(err)
next(err)
}
}
In order to be able to perform searches on the Recipe model, I slightly modified it and added the below. This tells mongoose to allow search in all fields.
recipeSchema.index({ '$**': 'text' })
On the frontend, the search bar has been implemented in different components but let us look at the homepage. When the user enter their search in the input field, an onChange event stores it in state searchData. The search bar itself is wrapped in a form which takes an onSubmit listener and triggers a callback function handleSubmit. The callback prevents the browser to reload the page and uses the history object push() method of react-router to redirect the user to the search results page and store the searchData on the location object of react-router.
function handleSubmit(event) {
event.preventDefault()
try {
history.push({ pathname: '/search', state: searchData })
} catch (err) {
console.log(err)
}
}
Now react renders the SearchResults component and the searchData state is updated with the location.state we passed from the homepage (this is the user input). We pass searchData as query in the URL to make a request to our API in a useEffect and store the recipe data returned in state recipeData. We then use this data to display and filter the results on the page for the user to see.
const SearchResults = ({ location, history }) => {
const [recipeData, updateRecipeData] = useState([])
const [searchData, updateSearchData] = useState(location.state)
const [filterSelected, updateFilterSelected] = useState('All')
useEffect(() => {
axios.get('/api/search', { params: { q: searchData } })
.then(({ data }) => {
updateRecipeData(data)
})
}, [])
Should a user do a new search from this page, the same logic applied in the Home component applies here. Even thought the below line in the handleSubmit() function pushes to the page the user is already on, it ensures the state is passed to the react-router location object. This becomes important if the user click on a recipe to see more details and wish to go back to the results. Without this line, the original state would still be stored on the location object and the user will be showed their first search results rather than the most recent.
history.push({ pathname: '/search', state: searchData })
- When a user visit a recipe already saved on their profile, the "Add to saved recipe" toggle is not on
- When reviewing a recipe, the rating cannot be selected before the review is written
- When a user updates their profile, on click of "Update your profile" the pop up doesn't closed and changes only show after the page is refreshed
- The filtering on "All Recipes" does not support multi category filters (i.e. combining "Diet" filters with "Allergens" filters)
- Break our Frontend into more components and reuse them to avoid code repetition (.i.e. search bar)
- Working as a Team has been such a pleasure
- Linking backend and frontend, being able to tailor our backend to serve our frontend needs
- Going further with React and building more complex components
- Building a search and a filtering functionality
- Having a first introduction to automated tests
- Seeding has been challenging, we were limited in the Public API calls to first feed our database
- Filtering by Diet, Lifestyle and Allergens on the "All Recipes" page has been tricky with a lot of moving parts and data to store in state
- Planning is absolutely key and has really helped delivering our app on time
- Making sure backend and frontend work well together
- Reading documentations extensively whether it was to use a new library or debug issues
- Working with Git branches, such a great way to collaborate and align with the team
- Deploying on Heroku








