https://ga-booker.herokuapp.com/
- Brief
- Team
- Overview
- Technologies
- Team Organisation
- Development process
- Work allocation
- Testing
- ChallengesWins
- Wins
- Future Features
With a time frame of 7 days, create a full-stack application using an Express API to serve data from a Mongo database. The front-end should be built with React, with multiple relationships and CRUD functionality for at least a couple of models. The app should have automated testing on at least one RESTful resource on the backend.
In alphabetical order:
Name | GitHub |
---|---|
Orjon | https://github.com/orjon |
Ru | https://github.com/RuLette |
Sumi | https://github.com/SumiSastri |
Wesley | https://github.com/wesley-hall |
Booker is a web app book sharing community - where users share their book collections by loaning out and borrowing books from other user.
- The user can register, login and update or delete their user profile
- The user upload their books to the app and create a library of books so that other users can borrow them
- The user can view other peoples’ books and request to borrow books from their libraries
- The user can accept or reject a request to borrow the books in their library
- The user can know how far the books they want to borrow are and where other libraries are on a map
- They can see the book title, author, reviews and ratings of books to make decisions whether to borrow the book or not
- The user can view books out on loan and the books they are loaning in one place
Viewing all books, filtering by library, searching for a specific book and then rating and reviewing it. |
---|
Front End | Back End | Testing | Other |
---|---|---|---|
React | Node.js | Mocha | yarn |
ReactDOM | MongoDB (NoSQL) | Chai | Webpack |
React Router DOM | Express | SuperTest | Babel |
Bulma | Mongoose | Axios | |
SCSS | mongoose-autopopulate | ||
Mapbox GL JS | JSON Web Tokens (JWT) | ||
bcrypt | |||
dotenv |
Our team met every morning so we could communicate our progress and we could discuss what to prioritise for the day. During the project we sat near one another and we were able to communicate our workflow. This also allowed rapid decision making when any of the team encountered an issue with their individual task at hand.
Team goals:
- Team is self-organising
- Decisions are made democratically
- Trouble shoot early and often
- Support quickly and solve problem
- Seek to solve the problem with root cause analysis
- The whole team is responsible for positive outcomes and good quality code
- Interactions better than documentation
The development process began with our team agreeing on the functionality we wanted in our application. Early in the development stage we broke down all the application's functions into groups that would become the 'pages' of the application.
We first began creating wireframes to work out this structure and general content placement of the website. These were sketched out on pieces of paper and the arrangement of these helped us to map out a clear user journey, and separate concerns.
This was an iterative, sometimes subjective, but ultimately very constructive process. Sketching out the user flows in this way greatly assisted in structuring the code and filing.
When we had decided our workflow we decided all team members should experience working on the backend and front end of the application.
In this project, I created the book schema, book schema testing and seeds in the back end of the application, and the user profiles and forms in the front end.
In the back end I first created a schema for books.
const bookSchema = new mongoose.Schema({
title: {type: String, required: true},
authors: {type: String},
image: {type: String},
fiction: {type: Boolean, required: true},
genre: { type: mongoose.Schema.ObjectId, ref: 'BookGenre'},
description: {type: String},
rating: [ratingSchema],
review: [reviewSchema],
owner: { type: mongoose.Schema.ObjectId, ref: 'User', autopopulate: true }
})
Book information with references to the BookGenre and User schemas, as well as information for book ratings and reviews.
const ratingSchema = new mongoose.Schema({
rating: {type: Number, min: 1, max: 5},
user: {type: mongoose.Schema.ObjectId, ref: 'User', autopopulate: true }
})
const reviewSchema = new mongoose.Schema({
review: {type: String},
user: {type: mongoose.Schema.ObjectId, ref: 'User', autopopulate: true }
})
Next I created a few seeds for books.
In the seeds file, JavaScript promises were used to ensure that the database is always seeded in the correct order. This is because certain data models require others to exist before they can be created:
- Books can only be created once users (book owners) and genres have been created
- Loans can only be created once users and books have been created
const promiseArray = [
User.create([...]),
BookGenre.create([...])
]
Promise.all(promiseArray)
.then(data => {
const [ users, genres ] = data
return Promise.all([
Books.create([...]),
users
])
})
.then(data => {
const [ books, users ] = data
return Loan.create([...])
})
I used the seeds to test the book schema I created, along with the CRUD routes to ensure it was working.
I then created a test resource for the books using chai and mocha. Supertest was a library installed to make HTTP calls within the test environment. This meant that in the test file, a local test environment could be created with 'dummy data'.
The dummy book data used for the test was named in a const called bookData. As defined in our book schema, fields such as the title and fiction are required and a test would not pass unless these two fields were filled.
const bookData = {
title: 'The Hobbit',
authors: 'J.R.R Tolkien',
image: 'http://www.orjon.com/dev/booker/images/bookcovers/cover-theHobbit.jpeg',
fiction: true,
description: ' In a hole in the ground there lived a hobbit. Not a nasty, dirty, wet hole, filled with the ends of worms and an oozy smell, nor yet a dry, bare, sandy hole with nothing in it to sit down on or to eat: it was a hobbit-hole, and that means comfort.'
}
As some routes such as posting a book required user authentification, the user environment and JWT token had to be imported into the test environment.
beforeEach(done => {
Book.collection.remove()
Book.create(
bookData
)
.then(() => User.remove({}))
.then(() => User.create({
username: 'test',
email: 'test',
password: 'test',
passwordConfirmation: 'test',
location: {
lat: 51.4,
lng: 21
},
libraryName: 'test'
}))
.then(user => {
token = jwt.sign({ sub: user._id }, secret, { expiresIn: '6h' })
done()
})
.catch(done)
})
describe('POST /api/books', () => {
it('should return a 201 response', done => {
api
.post('/api/books')
.set({ 'Accept': 'application/json', 'Authorization': `Bearer ${token}`})
.send(bookData)
.end((err, res) => {
console.log(err)
expect(res.status).to.eq(201)
done()
})
})
When I had completed testing for the book add and creation routes, more functionality was added to the application by allowing the logged in user to update their details, or delete their profile.
Complete CRUD cycle for users:
CRUD | API Route | HTTP Method |
---|---|---|
Create | /api/register | POST |
Read | /api/users /api/users/:id |
GET |
Update | /api/users/:id | PUT |
Delete | /api/users/:id | DELETE |
function userShow(req, res) {
User
.findById(req.params.id)
.then(user => res.status(200).json(user))
.catch(err => res.json(err))
}
If the user deletes their account, the books and loans in relation to their account has to be deleted as well.
handleDelete(e) {
e.preventDefault()
if (window.confirm('Delete the item?')) {
axios.delete(`/api/users/${Auth.getPayload().sub}`,
{ headers: { Authorization: `Bearer ${Auth.getToken()}`}})
.then(() => {
Auth.logout()
this.props.history.push('/')
})
.catch(err => this.setState({errors: err.response.data.errors}))
}
}
Hence the userDelete function in the backend required a promise to be written so that the books belonging to the user could be deleted as well.
function userDelete(req, res) {
const promiseArray = [
Book
.remove({owner: req.params.id}),
User
.findByIdAndRemove(req.params.id)
.exec()
]
Promise.all(promiseArray)
.then(() => res.sendStatus(204))
.catch(err => res.status(500).json(err))
}
Our group decided to keep the interface simple and intuitive to use. Hence styling was implemented using the Bulma CSS framework. Bulma has classes which are structured greatly speed up the process of creating grid layouts in particular, such as we used for the Books (All) page.
- Creating the promise functions in the seeds file - figuring out the order of promises needed
- Creating the test files - had to create a proxy user to test functionality
- Nav bar bugs - challenges logging out users
- Figuring out search and filter functions in React - pulling data into the render function
- User profile - giving users the ability to set their own location using a map marker
- Loans - scoping features and functions to fit time lines
- Key technologies used by everyone
- User journeys well mapped out and data-flows discussed in detail
- Good road-maps to map out back-log
- Testing started early
- In future we could add user to user messaging within the app
- Users could be allowed to borrow other library items, like record collections and other multimedia in future